Files
blog/studio-cli.html
T

307 lines
15 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>One Binary to Rule Them All: Our Studio CLI — Tinqs Blog</title>
<meta name="description" content="A single Go binary that gives AI agents full context about your machine, project, and services in 100ms. Screenshots, cloud vision, health checks — one install, every machine.">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://www.tinqs.com/blog/studio-cli">
<meta property="og:type" content="article">
<meta property="og:url" content="https://www.tinqs.com/blog/studio-cli">
<meta property="og:title" content="One Binary to Rule Them All: Our Studio CLI">
<meta property="og:description" content="One Go binary for machine identity, screenshots, cloud vision, and AI agent context.">
<meta property="og:image" content="https://www.tinqs.com/img/og-cover.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="One Binary to Rule Them All: Our Studio CLI">
<meta name="twitter:description" content="One Go binary for machine identity, screenshots, cloud vision, and AI agent context.">
<meta name="twitter:image" content="https://www.tinqs.com/img/og-cover.jpg">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "One Binary to Rule Them All: Our Studio CLI",
"datePublished": "2026-05-18",
"author": {
"@type": "Person",
"name": "Ozan Bozkurt"
},
"publisher": {
"@type": "Organization",
"name": "Tinqs Limited",
"url": "https://www.tinqs.com"
},
"description": "A single Go binary that gives AI agents full context about your machine, project, and services in 100ms. Screenshots, cloud vision, health checks — one install, every machine."
}
</script>
<!-- PostHog (EU) -->
<script>
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
posthog.init('phc_teG6p5oxf6poQHPThq5AGKzWQNhw4bHW9arLwWAVXm3f',{api_host:'https://eu.i.posthog.com',ui_host:'https://eu.posthog.com',person_profiles:'identified_only',defaults:'2026-01-30'})
</script>
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../style.css">
2026-06-02 22:50:20 +01:00
<style>
/* ── Team guide aesthetic: self-contained overrides ── */
/* ── Gradient title (amber → warm gold, hint of blue) ── */
.post__title {
background: linear-gradient(90deg, #c9935a, #f59e0b 40%, #38bdf8);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 800;
}
/* ── Date pill ── */
.post__date {
display: inline-block;
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
font-size: 0.72rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: #38bdf8;
border: 1px solid rgba(147, 140, 129, 0.25);
border-radius: 999px;
padding: 4px 14px;
margin-bottom: 16px;
}
/* ── Lead ── */
.post__lead {
color: #9aa7b4;
font-size: 1.08rem;
line-height: 1.7;
}
/* ── H2: left accent bar ── */
.post__body h2 {
font-size: 1.7rem;
margin: 54px 0 6px;
padding-left: 16px;
border-left: 4px solid #c9935a;
}
/* ── H3: purple secondary accent ── */
.post__body h3 {
color: #a855f7;
font-size: 1.18rem;
margin: 30px 0 4px;
}
/* ── Inline code ── */
.post__body code {
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
font-size: 0.86em;
background: #1c2230;
color: #9fe6c0;
padding: 2px 6px;
border-radius: 5px;
border: 1px solid #2a3340;
}
/* ── Code blocks (dark panel) ── */
.post__body pre {
background: #0a0e14;
border: 1px solid #2a3340;
border-radius: 10px;
padding: 16px 18px;
overflow-x: auto;
margin: 14px 0;
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
font-size: 0.85rem;
line-height: 1.55;
color: #e6edf3;
}
/* Reset inline-code double-up inside pre */
.post__body pre code {
background: transparent;
padding: 0;
border: none;
font-size: inherit;
color: inherit;
border-radius: 0;
}
/* ── Blockquote callout (ready for future use; build.js does not emit blockquote yet) ── */
.post__body blockquote {
background: rgba(245, 158, 11, 0.08);
border: 1px solid rgba(245, 158, 11, 0.25);
border-left: 4px solid #f59e0b;
border-radius: 0 12px 12px 0;
padding: 16px 18px;
margin: 18px 0;
color: #f4e3c4;
font-size: 0.94rem;
}
/* ── Links ── */
.post__body a {
color: #38bdf8;
}
.post__body a:hover {
color: #a855f7;
}
/* ── Strong ── */
.post__body strong {
color: #f59e0b;
}
/* ── HR ── */
.post__body hr {
border: none;
border-top: 1px solid #2a3340;
margin: 32px 0;
}
/* ── Figures ── */
.post__body figure img {
border-radius: 12px;
border: 1px solid #2a3340;
}
.post__body figcaption {
color: #9aa7b4;
font-size: 0.85rem;
margin-top: 6px;
}
/* ── List spacing ── */
.post__body li {
margin: 4px 0;
}
</style>
</head>
<body>
<!-- NAV -->
<nav class="nav nav--scrolled" id="nav">
<a href="/" class="nav__logo" aria-label="Tinqs home">
<span class="nav__wordmark">TINQS</span>
</a>
<div class="nav__links">
<a href="/#game" class="nav__link">Games</a>
<a href="/#tech" class="nav__link">Technology</a>
<a href="/#about" class="nav__link">About</a>
<a href="/blog/" class="nav__link" style="color: var(--c-accent-l);">Blog</a>
<a href="/#signup" class="nav__link">Contact</a>
<a href="/press" class="nav__link">Press</a>
</div>
<button class="nav__burger" aria-label="Open menu" id="navBurger">
<span></span><span></span><span></span>
</button>
</nav>
<!-- MOBILE MENU -->
<div class="mobile-menu" id="mobileMenu">
<a href="/#game" class="mobile-menu__link">Games</a>
<a href="/#tech" class="mobile-menu__link">Technology</a>
<a href="/#about" class="mobile-menu__link">About</a>
<a href="/blog/" class="mobile-menu__link">Blog</a>
<a href="/#signup" class="mobile-menu__link">Contact</a>
<a href="/press" class="mobile-menu__link">Press</a>
</div>
<!-- POST -->
<article class="post">
<a href="/blog/" class="post__back">&larr; All Posts</a>
<span class="post__date">18 May 2026</span>
<h1 class="post__title">One Binary to Rule Them All: Our Studio CLI</h1>
<p class="post__lead">Every AI agent session starts the same way: cold. The agent doesn't know what project this is, who's asking, what tools are available, or what happened yesterday. You spend the first five minutes re-explaining context.</p>
<div class="post__body">
<p>Our CLI solves this in 100ms. One command — <code>tstudio identity</code> — and the agent knows everything. The binary is 15MB, has zero runtime dependencies, and runs on every machine in the studio.</p>
<h2>The identity command (100ms)</h2>
<p>When an agent starts, the first thing it calls is <code>tstudio identity</code>. The output:</p>
<ul>
<li><strong>Soul file</strong> — the agent's persistent identity, values, operating principles</li>
<li><strong>Company context</strong> — team members, roles, what the company does</li>
<li><strong>Machine context</strong> — hostname, OS, which repos are cloned, what services are running</li>
<li><strong>Ecosystem</strong> — other repos and their purpose</li>
<li><strong>Service status</strong> — which URLs are live and reachable</li>
</ul>
<p>This data lives in markdown files in the docs repo. Any machine on the network can read it. The agent goes from blank to fully contextual in under a second.</p>
<p>This started as a convenience tool for humans. It became the single most important function in our stack. Every agent session — Cursor, Claude Code, Pi — starts with <code>tstudio identity</code>. Without it, every conversation begins with "let me explain the project." With it, the agent already knows.</p>
<h2>Screenshots and cloud vision</h2>
<p>The CLI can capture any window from outside the process. No in-game overlay, no rendering pipeline integration. OS-level capture — GDI+ on Windows, screencapture on Mac.</p>
<p>A <code>photo</code> command sends the screenshot to a cloud vision model. The agent says "take a photo of the game" and gets back: "The player character is standing near a half-built hut. Three palm trees to the left. The terrain has a visible seam between two biomes."</p>
<p>This is how you file bugs without typing. Look at the game, tell the agent what's wrong. It takes a screenshot, describes what it sees, and creates an issue with both the description and the image attached. Keyboard-free bug reporting.</p>
<h2>Health checks</h2>
<p><code>tstudio doctor</code> runs a comprehensive check:</p>
<ul>
<li>Is the git platform reachable and authenticated?</li>
<li>Is the game server running?</li>
<li>Are all expected repos cloned and on the right branch?</li>
<li>Are required tools installed at the right version?</li>
</ul>
<p>Output is a green/yellow/red table. Essential for unattended agent sessions — the agent verifies its environment before starting work. No "the build failed because port 3000 was already taken" at 3am.</p>
<h2>Why Go</h2>
<p>Go compiles to a single static binary. No Python virtualenvs, no Node.js version managers, no DLL hell on Windows. The same binary runs on a gaming PC, a designer's MacBook, and a CI runner in AWS.</p>
<p>Cross-compilation is trivial. We build Windows, Mac (arm64 + amd64), and Linux binaries from a single CI workflow. Push a tag, CI builds all three, uploads to S3. The binary is 15MB, starts in under 100ms, has zero runtime dependencies.</p>
<h2>What we learned</h2>
<p><strong>The CLI is the API for AI agents.</strong> What started as a human convenience tool became the primary interface for agents. Every session starts with <code>tstudio identity</code>. The agent's "hands and eyes" — screenshots, vision, health checks — are subcommands of the same binary.</p>
<p><strong>One binary beats ten scripts.</strong> Scripts rot. They have different shells, different PATH assumptions, different error handling. A compiled binary either works or it doesn't. It ships with dependencies baked in. It doesn't care if your Python is 3.9 or 3.12.</p>
<p><strong>Cloud vision is underrated for game dev.</strong> Sending a screenshot to a vision model sounds gimmicky. In practice, it's the fastest way to document visual bugs. "The tree is floating 2m above the terrain" is much faster to communicate when the AI is looking at the same screen.</p>
<p><strong>Agent cold starts are the real problem.</strong> Without the identity system, every session starts with the agent asking "what project is this?" With it, the agent knows everything in 100ms. That's the difference between an AI assistant and an AI team member.</p>
<hr>
<p><em>The CLI is part of <a href="https://tinqs.com" style="color: var(&ndash;c-accent-l);">Tinqs Studio</a>. Every time we find ourselves about to write a script that needs to work on multiple machines, we add a subcommand instead. One binary that makes the studio work — whether the operator is human or AI.</em></p>
</div>
<div class="post__author">
<div class="post__author-avatar">OB</div>
<div class="post__author-info">
<span class="post__author-name">Ozan Bozkurt</span><br>
CTO & Developer, Tinqs
</div>
</div>
</article>
<!-- FOOTER -->
<footer class="footer">
<div class="footer__inner">
<span class="footer__wordmark">TINQS</span>
<div class="footer__links">
<a href="/#game">Games</a>
<a href="/#tech">Technology</a>
<a href="/#about">About</a>
<a href="/blog/">Blog</a>
<a href="mailto:hello@tinqs.com">hello@tinqs.com</a>
<a href="/press">Press Kit</a>
</div>
<p class="footer__copy">Tinqs Limited &mdash; London, est. 2020</p>
</div>
</footer>
<script>
const burger = document.getElementById('navBurger');
const mobileMenu = document.getElementById('mobileMenu');
burger.addEventListener('click', () => {
const open = mobileMenu.classList.toggle('mobile-menu--open');
burger.classList.toggle('nav__burger--open', open);
document.body.style.overflow = open ? 'hidden' : '';
});
mobileMenu.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
mobileMenu.classList.remove('mobile-menu--open');
burger.classList.remove('nav__burger--open');
document.body.style.overflow = '';
});
});
</script>
</body>
</html>