insta 0.0.82 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -29
- package/dist/agent.js +2 -2
- package/dist/api.js +19 -4
- package/dist/build-logs.js +32 -8
- package/dist/commands/agent-policy.js +1 -1
- package/dist/commands/auth.js +32 -18
- package/dist/commands/billing.js +5 -5
- package/dist/commands/compute.js +83 -41
- package/dist/commands/db-query.js +18 -14
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/domain.js +49 -2
- package/dist/commands/env.js +1 -1
- package/dist/commands/managed-db.js +34 -0
- package/dist/commands/mcp.js +2 -2
- package/dist/commands/metrics.js +6 -5
- package/dist/commands/observe.js +2 -2
- package/dist/commands/{db.js → postgres.js} +32 -32
- package/dist/commands/regions.js +1 -1
- package/dist/commands/services.js +31 -70
- package/dist/commands/setup.js +8 -7
- package/dist/commands/storage.js +17 -2
- package/dist/commands/template.js +4 -4
- package/dist/commands/upgrade.js +20 -8
- package/dist/config.js +49 -31
- package/dist/deploy-archive.js +65 -56
- package/dist/ensure-skills.js +1 -1
- package/dist/index.js +227 -161
- package/dist/observe/hook.js +1 -1
- package/dist/observe/install.js +1 -1
- package/dist/resolve-service.js +4 -4
- package/dist/telemetry.js +8 -8
- package/dist/util.js +9 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
2
|
+
import { Command, Option } from 'commander';
|
|
3
3
|
import { configureAgent, detectAgent } from './agent.js';
|
|
4
|
+
import { setApiUrlOverride } from './config.js';
|
|
4
5
|
import * as agentPolicy from './commands/agent-policy.js';
|
|
5
6
|
import { ApiError, AgentApprovalRequired } from './api.js';
|
|
6
7
|
import { CliCancel, CliExit, fail, relayedExitCode } from './util.js';
|
|
@@ -24,8 +25,9 @@ import { build } from './commands/build.js';
|
|
|
24
25
|
import { buildLogs } from './commands/build-logs.js';
|
|
25
26
|
import * as computeCmd from './commands/compute.js';
|
|
26
27
|
import * as githubCmd from './commands/github.js';
|
|
27
|
-
import * as
|
|
28
|
+
import * as pgCmd from './commands/postgres.js';
|
|
28
29
|
import * as dbQueryCmd from './commands/db-query.js';
|
|
30
|
+
import * as managedDb from './commands/managed-db.js';
|
|
29
31
|
import * as storageCmd from './commands/storage.js';
|
|
30
32
|
import { manifest } from './commands/manifest.js';
|
|
31
33
|
import * as template from './commands/template.js';
|
|
@@ -76,8 +78,13 @@ const program = new Command();
|
|
|
76
78
|
// group's own options only match before the subcommand name, so occurrences after it are matched
|
|
77
79
|
// against the subcommand's own (identically-named) option instead.
|
|
78
80
|
program.enablePositionalOptions();
|
|
79
|
-
program.name('insta').description('InstaCloud CLI — manage projects, branches,
|
|
81
|
+
program.name('insta').description('InstaCloud CLI — manage projects, branches, services, deploys').version(cliVersion());
|
|
80
82
|
program.option('--agent', 'run as an agent with a verified project session and project agent policy');
|
|
83
|
+
program.option('--api-url <url>', 'control-plane API base URL for this invocation only — beats INSTA_API_URL, INSTA_ENV and the stored login; a URL for another deployment runs logged-out (internal debugging). Accepted before or after any subcommand (except `compute exec`: pass it before `compute` there)');
|
|
84
|
+
// The runtime --api-url must be in place before any action loads config (ApiClient.load →
|
|
85
|
+
// readGlobal). optsWithGlobals merges the root's, a group's and the leaf's copy of the flag
|
|
86
|
+
// (addApiUrlEverywhere below), so it is honoured wherever it was typed; typed twice, the outermost wins.
|
|
87
|
+
program.hook('preAction', (_root, action) => setApiUrlOverride(action.optsWithGlobals().apiUrl));
|
|
81
88
|
program.hook('preAction', () => configureAgent(detectAgent(!!program.opts().agent)));
|
|
82
89
|
// ---- auth ----
|
|
83
90
|
program.command('login').description('Log in — bare: sign in from your browser (any account type); or --email <email> + password, --oauth <github|google>, --device (headless), --claim <email> (agent: the named user confirms a code), --api-key <insta_…> (headless, durable token)')
|
|
@@ -90,36 +97,14 @@ program.command('login').description('Log in — bare: sign in from your browser
|
|
|
90
97
|
.option('--api-url <url>', 'control-plane API base URL')
|
|
91
98
|
.option('--env <name>', `deployment environment: ${ENV_NAMES.join(' | ')}`)
|
|
92
99
|
.action(guard((o) => auth.login(o)));
|
|
93
|
-
program.command('logout').description('Log out and clear local tokens').action(guard(() => auth.logout()));
|
|
100
|
+
program.command('logout').description('Log out and clear local tokens — always the stored session, so --api-url (and INSTA_API_URL / INSTA_ENV) do not apply here').action(guard(() => auth.logout()));
|
|
94
101
|
program.command('status').description('Show login + linked project').option('--json').action(guard((o) => auth.status(o)));
|
|
95
|
-
// ---- environment (prod | staging) ----
|
|
96
|
-
const envCmd = program.command('env').description('Show or switch the deployment environment (prod | staging)');
|
|
102
|
+
// ---- environment (prod | staging) — hidden: `--api-url` covers the debugging case; kept working ----
|
|
103
|
+
const envCmd = program.command('env', { hidden: true }).description('Show or switch the deployment environment (prod | staging)');
|
|
97
104
|
envCmd.command('show', { isDefault: true }).description('Show the current environment and its hosts')
|
|
98
105
|
.option('--json').action(guard((o) => envCmd_.envShow(o)));
|
|
99
106
|
envCmd.command('use <name>').description(`Switch environment (${ENV_NAMES.join(' | ')}) — drops the stored session, which is deployment-specific`)
|
|
100
107
|
.option('--json').action(guard((name, o) => envCmd_.envUse(name, o)));
|
|
101
|
-
// ---- run (per-request secret injection — nothing written to disk) ----
|
|
102
|
-
program.command('run <cmd> [args...]').description('Run a command with the branch credential bundle injected into its environment (no .env written)')
|
|
103
|
-
.option('--branch <b>', 'branch bundle to inject (default: linked branch)')
|
|
104
|
-
.option('--service <type/name>', "inject one compute service's own slice of the branch bundle, e.g. compute/api — the unambiguous read when several services define the same name (NOT the container's env: it also carries the branch's provider credentials, which a container gets only where bound)")
|
|
105
|
-
.option('--ignore-collisions', 'run even when several services define the same name; every such name is REMOVED from the child environment (never inherited from your shell)')
|
|
106
|
-
.passThroughOptions().allowUnknownOption()
|
|
107
|
-
.action(guard((cmd, args, o) => runCmd.run([cmd, ...(args ?? [])], o)));
|
|
108
|
-
// ---- agent setup (the `curl … | sh --agents` target) ----
|
|
109
|
-
const setupCmd = program.command('setup').description('Set up this machine for InstaCloud agent workflows');
|
|
110
|
-
setupCmd.command('agent').description('Install the insta CLI (if missing), the insta skill for all coding agents, and the MCP server — targets production; pass --env staging for the staging deployment')
|
|
111
|
-
.option('-y, --yes', 'non-interactive')
|
|
112
|
-
.option('--env <prod|staging>', 'deployment to set this machine up for (default: prod — switches and persists, like `insta env use`)')
|
|
113
|
-
.option('--mcp-token', 'register Claude Code with a minted insta_ API token instead of OAuth (requires login and token-creation permission)')
|
|
114
|
-
.option('--project <id>', 'also link this directory to an existing project after setup (flows through login first if needed)')
|
|
115
|
-
.option('--create [name]', 'also create a new project and link this directory after setup (default name: this directory; mutually exclusive with --project)')
|
|
116
|
-
.action(guard((o) => setup.setupAgent(o)));
|
|
117
|
-
// ---- MCP server integration ----
|
|
118
|
-
const mcpCmd = program.command('mcp').description('insta-cloud remote MCP server integration');
|
|
119
|
-
mcpCmd.command('install').description('Register the remote MCP server with coding agents (default: Claude Code + all detected)')
|
|
120
|
-
.option('--agent <slug>', 'one agent: claude-code, cursor, codex, opencode, copilot, factory-droid')
|
|
121
|
-
.option('--mcp-token', 'claude-code only: minted insta_ API token instead of OAuth (requires login and token-creation permission)')
|
|
122
|
-
.action(guard((o) => mcp.mcpInstall(o)));
|
|
123
108
|
// ---- org ----
|
|
124
109
|
const orgCmd = program.command('org').description('Manage organizations');
|
|
125
110
|
orgCmd.command('list').option('--json').action(guard((o) => org.orgList(o)));
|
|
@@ -138,14 +123,14 @@ br.command('switch <name>').option('--json').action(guard((name, o) => branch.br
|
|
|
138
123
|
br.command('delete <name>').option('--json').action(guard((name, o) => branch.branchDelete(name, o)));
|
|
139
124
|
br.command('merge <source>').description('Merge a branch service set into another (structural, no data)')
|
|
140
125
|
.option('--into <branch>', 'target branch (default: current)').option('--json').action(guard((source, o) => branch.branchMerge(source, o)));
|
|
141
|
-
// ----
|
|
142
|
-
const svc = program.command('
|
|
126
|
+
// ---- service (opt-in postgres/storage/compute/redis/mysql/mongodb) ----
|
|
127
|
+
const svc = program.command('service').aliases(['services', 'svc']).description('Manage project services: add / list / remove / rename (postgres|storage|compute|redis|mysql|mongodb)');
|
|
143
128
|
// [type] [name] are optional so the command can answer "what can I add?" — a terminal is walked
|
|
144
129
|
// through the dashboard's Add Service kinds, anything else gets that list back as an error
|
|
145
130
|
// (resolve-service.ts). Picking Docker Image also fills in --image/--port from the answers.
|
|
146
131
|
svc.command('add [type] [name]').description('Provision a service on demand (assigns a default domain for postgres/compute); with no type/name, a terminal picks from the service kinds')
|
|
147
132
|
.option('--branch <branch>', 'target branch (default: current)')
|
|
148
|
-
.option('--region <region>', 'region for postgres/compute/managed databases, e.g. us-east (see `insta regions`)')
|
|
133
|
+
.option('--region <region>', 'region for postgres/compute/managed databases, e.g. us-east (see `insta config regions`)')
|
|
149
134
|
.option('--public', 'storage only: serve the bucket with anonymous public-read (default private)')
|
|
150
135
|
.option('--image <url>', 'compute only: run this container image at creation')
|
|
151
136
|
.option('--port <n>', 'compute only: port the image listens on (default 8080)')
|
|
@@ -166,20 +151,15 @@ svc.command('remove <type> <name>').description('Remove a service and destroy it
|
|
|
166
151
|
svc.command('rename <type> <name> <new-name>').description('Rename a service and re-key its managed secret names')
|
|
167
152
|
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
168
153
|
.action(guard((type, name, newName, o) => services.servicesRename(type, name, newName, o)));
|
|
169
|
-
svc.command('set-access <type> <name> <access>').description('Set a storage service bucket access mode (access: public|private)')
|
|
170
|
-
.option('--json').action(guard((type, name, access, o) => services.servicesSetAccess(type, name, access, o)));
|
|
171
|
-
svc.command('scale <type> <name> <number> [region]').description('Set a compute service same-region replica count from 1 to 10 (paid plans only)')
|
|
172
|
-
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((type, name, number, region, o) => services.servicesScale(type, name, number, region, o)));
|
|
173
|
-
svc.command('upgrade <type> <name> <spec>').description('Change a compute service spec (paid plans only). Postgres upgrades are rejected by the platform — use `insta db limits` instead')
|
|
174
|
-
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((type, name, spec, o) => services.servicesUpgrade(type, name, spec, o)));
|
|
175
|
-
svc.command('secrets <type> <name>').description("List a service's secret names")
|
|
176
|
-
.option('--branch <b>').option('--json').action(guard((type, name, o) => services.servicesSecrets(type, name, o)));
|
|
177
154
|
// ---- secrets (seam) ----
|
|
178
155
|
const sec = program.command('secrets').description('Fetch the credential bundle (secret seam) into .env')
|
|
179
156
|
.option('--branch <branch>')
|
|
180
157
|
.option('--service <type/name>', "read one compute service's own slice of the bundle instead of the branch-wide merge, e.g. compute/api")
|
|
181
158
|
.option('-o, --output <file>', 'output file (default .env)').option('--print', 'print instead of writing').option('--json')
|
|
182
159
|
.action(guard((o) => secretsCmd.secrets(o)));
|
|
160
|
+
// commander 12 defaults allowExcessArguments to true, so a mistyped/retired subcommand (e.g.
|
|
161
|
+
// `secrets lst`) would otherwise run this group's own action instead of failing.
|
|
162
|
+
sec.allowExcessArguments(false);
|
|
183
163
|
sec.command('list').description('List secret names, grouped by service').option('--branch <branch>').option('--json').action(guard((o) => secretsCmd.secretsList(o)));
|
|
184
164
|
sec.command('set <name> [value]').description('Set a user secret (project-wide; value from stdin if omitted)')
|
|
185
165
|
.option('--branch <branch>', 'scope to one branch').option('--service <type/name>', 'bind to a branch service (implies current branch)')
|
|
@@ -210,31 +190,68 @@ sec.command('sources').description('List service credential sources available fo
|
|
|
210
190
|
.action(guard((o) => secretsCmd.secretsSources(o)));
|
|
211
191
|
sec.command('tree').description('Show secrets as project → branch → service → secrets').option('--json')
|
|
212
192
|
.action(guard((o) => secretsCmd.secretsTree(o)));
|
|
213
|
-
// ----
|
|
214
|
-
program.command('
|
|
215
|
-
|
|
216
|
-
.option('--
|
|
217
|
-
.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
.
|
|
222
|
-
|
|
223
|
-
.option('--
|
|
224
|
-
.
|
|
225
|
-
|
|
193
|
+
// ---- domain (bought here, or bring your own; hostnames on compute services; DNS of bought zones) ----
|
|
194
|
+
const dom = program.command('domain').description('Domains: buy through InstaCloud or bring your own — attach / check / detach hostnames on compute services; DNS records of bought domains');
|
|
195
|
+
dom.command('search <keyword>').description('Search purchasable names with prices (a label like "myapp" or a full name like "myapp.com")')
|
|
196
|
+
.option('--tlds <list>', 'comma-separated TLDs to include').option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
197
|
+
.action(guard((keyword, o) => domainCmd.domainSearch(keyword, o)));
|
|
198
|
+
dom.command('buy <name>').description('Buy a domain — pay at the printed Stripe Checkout link. It serves nothing until you attach it (gated: domain.purchase)')
|
|
199
|
+
.option('--years <n>', 'registration term in years (default 1)')
|
|
200
|
+
.option('--no-open', 'print the checkout URL instead of opening a browser').option('--json')
|
|
201
|
+
.action(guard((name, o) => domainCmd.domainBuy(name, o)));
|
|
202
|
+
dom.command('attach <hostname>').description('Point a hostname at a compute service. A domain bought here: `abc.com` binds it and its www, `api.abc.com` binds only that. A domain you own elsewhere: the DNS records to publish in your own zone are printed (gated: deploy)')
|
|
203
|
+
.option('--branch <b>').option('--group <g>', "compute service (default: the branch's sole compute service)").option('--json')
|
|
204
|
+
.action(guard((hostname, o) => domainCmd.domainAttach(hostname, o)));
|
|
205
|
+
dom.command('check <hostname>').description("A hostname's attach state — ownership TXT, routing CNAME, edge certificate, where it resolves — and what each still needs")
|
|
206
|
+
.option('--branch <b>').option('--group <g>', "compute service (default: the branch's sole compute service)").option('--json')
|
|
207
|
+
.action(guard((hostname, o) => domainCmd.domainCheck(hostname, o)));
|
|
208
|
+
dom.command('detach <hostname>').description('Detach a hostname from its compute service (gated: deploy). Bring-your-own hostnames only — a hostname under a domain bought here is moved with `insta domain attach`, which releases it from its current service')
|
|
209
|
+
.option('--branch <b>').option('--group <g>', "compute service (default: the branch's sole compute service)").option('--json')
|
|
210
|
+
.action(guard((hostname, o) => domainCmd.domainDetach(hostname, o)));
|
|
211
|
+
dom.command('list').description("Domains bought through InstaCloud in this org — a domain belongs to the org, each of its hostnames to a service")
|
|
212
|
+
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
213
|
+
.action(guard((o) => domainCmd.domainList(o)));
|
|
214
|
+
dom.command('status <name>').description("A bought domain's order and attach state")
|
|
215
|
+
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
216
|
+
.action(guard((name, o) => domainCmd.domainStatus(name, o)));
|
|
217
|
+
const rec = dom.command('records').description('DNS records of a bought domain — the zone InstaCloud holds at the registrar');
|
|
218
|
+
rec.command('list <domain>').description('Every record in the zone, managed ones marked')
|
|
219
|
+
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
220
|
+
.action(guard((domain, o) => domainCmd.domainRecordsList(domain, o)));
|
|
221
|
+
rec.command('add <domain> <type> <name> <content>').description('Add a record — type A|AAAA|CNAME|ANAME|MX|TXT|SRV|NS; name "@" for the domain itself, a label like "www", or the full hostname under it')
|
|
222
|
+
.option('--ttl <seconds>', 'time to live in seconds (default 300)').option('--priority <n>', 'MX and SRV only')
|
|
223
|
+
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
224
|
+
.action(guard((domain, type, name, content, o) => domainCmd.domainRecordsAdd(domain, type, name, content, o)));
|
|
225
|
+
rec.command('set <domain> <id>').description('Change a record by its id (from `records list`); fields you omit keep their value')
|
|
226
|
+
.option('--type <t>', 'A|AAAA|CNAME|ANAME|MX|TXT|SRV|NS').option('--name <host>', '"@" for the domain itself, a label like "www", or the full hostname under it').option('--content <value>', 'the answer').option('--ttl <seconds>', 'time to live in seconds').option('--priority <n>', 'MX and SRV only')
|
|
227
|
+
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
228
|
+
.action(guard((domain, id, o) => domainCmd.domainRecordsSet(domain, id, o)));
|
|
229
|
+
rec.command('remove <domain> <id>').description('Remove a record by its id (a record InstaCloud published for a live hostname is refused)')
|
|
230
|
+
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
231
|
+
.action(guard((domain, id, o) => domainCmd.domainRecordsRemove(domain, id, o)));
|
|
232
|
+
// logs/metrics live under each resource; the platform component is fixed by the parent. One
|
|
233
|
+
// registration path so the five groups cannot drift apart in flags or wording.
|
|
234
|
+
function addObservability(group, component, noun) {
|
|
235
|
+
group.command('metrics [service]').description(`${noun} metrics — last value per series (--json for the points)`)
|
|
236
|
+
.option('--branch <b>').option('--from <unix>').option('--to <unix>').option('--step <s>').option('--json')
|
|
237
|
+
.action(guard((service, o) => obs.metrics(component, service, o)));
|
|
238
|
+
const logs = group.command('logs [service]').description(component === 'db'
|
|
239
|
+
? `${noun} logs (runtime; a window pages ~7 days of history)`
|
|
240
|
+
: `${noun} logs (runtime by default; --deploy = machine lifecycle events)`)
|
|
241
|
+
.option('--branch <b>').option('--limit <n>').option('--region <r>').option('--instance <i>').option('--json')
|
|
242
|
+
.option('--from <t>', 'window start: unix seconds or ISO-8601 — pages history (~7-day retention); without a window one recent provider page (~100 lines) is returned')
|
|
243
|
+
.option('--to <t>', 'window end: unix seconds or ISO-8601 (default: now)')
|
|
244
|
+
.option('--since <dur>', 'relative window start, e.g. 90s, 30m, 2h, 1d (shorthand for --from now-dur)');
|
|
245
|
+
if (component !== 'db')
|
|
246
|
+
logs.option('--deploy', 'show deploy events (machine lifecycle) instead of runtime logs');
|
|
247
|
+
logs.action(guard((service, o) => obs.logs(component, service, o)));
|
|
248
|
+
}
|
|
226
249
|
// `insta compute exec` needs the command verbatim after a literal `--`; split it out of argv here,
|
|
227
250
|
// before commander parses anything (see splitExecArgs's own comment for why `service` being
|
|
228
251
|
// optional makes commander unable to hold that boundary itself).
|
|
229
252
|
const { argv: computeArgv, command: execCommand, windowsFallback: execWindowsFallback, } = computeCmd.splitExecArgs(process.argv);
|
|
230
|
-
// ---- compute
|
|
231
|
-
const compute = program.command('compute').description('
|
|
232
|
-
compute.command('set-domain <host>').description('Attach a custom domain to a branch compute service (gated: deploy)')
|
|
233
|
-
.option('--branch <b>').option('--group <g>').option('--json').action(guard((host, o) => computeCmd.setDomain(host, o)));
|
|
234
|
-
compute.command('check-domain <host>').description("Show a custom domain's cert status + required DNS records")
|
|
235
|
-
.option('--branch <b>').option('--group <g>').option('--json').action(guard((host, o) => computeCmd.checkDomain(host, o)));
|
|
236
|
-
compute.command('remove-domain <host>').description('Detach a custom domain (gated: deploy)')
|
|
237
|
-
.option('--branch <b>').option('--group <g>').option('--json').action(guard((host, o) => computeCmd.removeDomain(host, o)));
|
|
253
|
+
// ---- compute ----
|
|
254
|
+
const compute = program.command('compute').description('Compute services: lifecycle (start/stop/suspend/restart/status), scale, limits, volume, always-on, exec, ssh, GitHub source, logs, metrics');
|
|
238
255
|
compute.command('start [service]').description('Bring a compute service online (persistent — re-enables auto-wake)')
|
|
239
256
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeStart(service, o)));
|
|
240
257
|
compute.command('stop [service]').description('Take a compute service offline; traffic will NOT wake it until `start`')
|
|
@@ -245,6 +262,10 @@ compute.command('restart [service]').description("Restart a compute service by r
|
|
|
245
262
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeRestart(service, o)));
|
|
246
263
|
compute.command('status [service]').description("Show a compute service's desired vs. live state")
|
|
247
264
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeStatus(service, o)));
|
|
265
|
+
compute.command('scale <count> [service]').description('Set a compute service same-region replica count, 1 to 10 (paid plans only)')
|
|
266
|
+
.option('--region <region>', 'region to scale in (default: the service region)')
|
|
267
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
268
|
+
.action(guard((count, service, o) => computeCmd.computeScale(count, service, o)));
|
|
248
269
|
compute.command('limits [service]').description("Show or set a compute service's resource ceiling (any plan within the free cap; raising above it needs a paid plan). --memory is the dial; cpu derives from it unless --cpu is given. Billing is actual usage — the ceiling caps what the app may burn, it is not a price")
|
|
249
270
|
.option('--memory <size>', 'memory ceiling, e.g. 512mb or 1gb').option('--cpu <n>', 'vCPU ceiling override (provider sizes: 1, 2, 4, 6, 8)')
|
|
250
271
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeLimits(service, o)));
|
|
@@ -285,35 +306,58 @@ compute.command('volume [service]').description("Show, attach, grow, or delete a
|
|
|
285
306
|
.option('--size <gi>', 'new size in whole Gi, e.g. 10 (must be ≥ the current size)')
|
|
286
307
|
.option('--delete', 'destroy the volume and ALL its data (irreversible; download anything you need first)')
|
|
287
308
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeVolume(service, o)));
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
.action(guard((o) => dbCmd.dbConnect(o)));
|
|
296
|
-
db.command('limits').description("Show or set a postgres service's resource ceiling (any plan within the free cap, paid above it; insta-db-backed only). Moves both directions")
|
|
297
|
-
.option('--cpu <n>', "vCPU ceiling, e.g. 2 or 2500m").option('--memory <size>', "memory ceiling, e.g. 4Gi")
|
|
298
|
-
.option('--json').option('--branch <branch>', 'branch (default: current)').option('--group <g>', 'postgres service name (default: the sole/default one)')
|
|
299
|
-
.action(guard((o) => dbCmd.dbLimits(o)));
|
|
300
|
-
db.command('stats').description("Postgres stats snapshot: connections vs the server's max (active count), cache hit rate, database size. insta-db-backed services answer without waking a suspended instance")
|
|
301
|
-
.option('--json').option('--branch <branch>', 'branch (default: current)').option('--group <g>', 'postgres service name (default: the sole/default one)')
|
|
302
|
-
.action(guard((o) => dbCmd.dbStats(o)));
|
|
303
|
-
db.command('always-on <mode>').description('Set a postgres service always-on (mode: on|off). on = instance stays warm, no cold starts; off = default scale-to-zero (idle instance suspends; first connection cold-starts). insta-db-backed services only')
|
|
304
|
-
.option('--json').option('--branch <branch>', 'branch (default: current)').option('--group <g>', 'postgres service name (default: the sole/default one)')
|
|
305
|
-
.action(guard((mode, o) => dbCmd.dbAlwaysOn(mode, o)));
|
|
306
|
-
db.command('volume').description("Show or grow a postgres service's provisioned volume (block disk; insta-db-backed only). No --size: print size and the plan cap (any plan). --size grows it (paid plans; grow-only — a provisioned disk cannot shrink). Billing is actual data stored — the size is a cap, not a price")
|
|
307
|
-
.option('--size <gi>', 'new size in whole Gi, e.g. 10 (must be ≥ the current size)')
|
|
308
|
-
.option('--json').option('--branch <branch>', 'branch (default: current)').option('--group <g>', 'postgres service name (default: the sole/default one)')
|
|
309
|
-
.action(guard((o) => dbCmd.dbVolume(o)));
|
|
310
|
-
db.command('query <service> [args...]').description('Run a query/command against a managed database (mysql/redis/mongodb) via the console exec API. mysql/mongodb take one quoted statement; redis takes a pre-tokenized argv (e.g. `GET mykey`). Not for postgres — use `insta db url|connect` / the SQL editor')
|
|
311
|
-
.option('--database <db>', 'mongodb only — the database to run against (default admin)')
|
|
309
|
+
addObservability(compute, 'compute', 'compute');
|
|
310
|
+
// ---- postgres ----
|
|
311
|
+
const pg = program.command('postgres').description('Postgres services: connection string, psql, stats, resource ceiling, volume, always-on, logs, metrics');
|
|
312
|
+
pg.command('url [service]').description('Print the postgres connection string (DSN) — bare on stdout for piping, e.g. `psql "$(insta postgres url)"` (gated: secrets.read). Provider credentials are not in `insta secrets` — this is the command that yields the DSN')
|
|
313
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
314
|
+
.action(guard((service, o) => pgCmd.dbUrl(service, o)));
|
|
315
|
+
pg.command('connect [service]').description("Open an interactive psql session on the postgres service (needs psql on PATH; gated: secrets.read). A suspended instance wakes on connect — the first prompt can take a few seconds. Exits with psql's own exit code")
|
|
312
316
|
.option('--branch <branch>', 'branch (default: current)')
|
|
313
|
-
.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
+
.action(guard((service, o) => pgCmd.dbConnect(service, o)));
|
|
318
|
+
pg.command('stats [service]').description("Postgres stats snapshot: connections vs the server's max (active count), cache hit rate, database size. insta-db-backed services answer without waking a suspended instance")
|
|
319
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
320
|
+
.action(guard((service, o) => pgCmd.dbStats(service, o)));
|
|
321
|
+
pg.command('limits [service]').description("Show or set a postgres service's resource ceiling (any plan within the free cap, paid above it; insta-db-backed only). Moves both directions")
|
|
322
|
+
.option('--cpu <n>', 'vCPU ceiling, e.g. 2 or 2500m').option('--memory <size>', 'memory ceiling, e.g. 4Gi')
|
|
323
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
324
|
+
.action(guard((service, o) => pgCmd.dbLimits(service, o)));
|
|
325
|
+
pg.command('volume [service]').description("Show or grow a postgres service's provisioned volume (block disk; insta-db-backed only). No --size: print size and the plan cap (any plan). --size grows it (paid plans; grow-only — a provisioned disk cannot shrink). Billing is actual data stored — the size is a cap, not a price")
|
|
326
|
+
.option('--size <gi>', 'new size in whole Gi, e.g. 10 (must be ≥ the current size)')
|
|
327
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
328
|
+
.action(guard((service, o) => pgCmd.dbVolume(service, o)));
|
|
329
|
+
pg.command('always-on <mode> [service]').description('Set a postgres service always-on (mode: on|off). on = instance stays warm, no cold starts; off = default scale-to-zero (idle instance suspends; first connection cold-starts). insta-db-backed services only')
|
|
330
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
331
|
+
.action(guard((mode, service, o) => pgCmd.dbAlwaysOn(mode, service, o)));
|
|
332
|
+
addObservability(pg, 'db', 'postgres');
|
|
333
|
+
// ---- redis / mysql / mongodb (managed Fly databases) ----
|
|
334
|
+
for (const type of ['redis', 'mysql', 'mongodb']) {
|
|
335
|
+
const g = program.command(type).description(`Managed ${type} services: query, status, resource ceiling, volume, always-on, logs, metrics`);
|
|
336
|
+
const query = g.command('query <service> [args...]').description(type === 'redis'
|
|
337
|
+
? 'Run a redis command against the service via the console exec API — a pre-tokenized argv, e.g. `GET mykey`'
|
|
338
|
+
: `Run one quoted ${type} statement against the service via the console exec API`)
|
|
339
|
+
.option('--branch <branch>', 'branch (default: current)').option('--json');
|
|
340
|
+
if (type === 'mongodb')
|
|
341
|
+
query.option('--database <db>', 'the database to run against (default admin)');
|
|
342
|
+
query.action(guard((service, args, o) => dbQueryCmd.dbQuery(service, args, o, undefined, type)));
|
|
343
|
+
g.command('status [service]').description(`A ${type} service's live runtime health: healthy | crashed | starting | standby (scaled to zero, wakes on request — normal) | none | unknown`)
|
|
344
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
345
|
+
.action(guard((service, o) => managedDb.managedStatus(type, service, o)));
|
|
346
|
+
g.command('limits [service]').description(`Show or set a ${type} service's resource ceiling (any plan within the free cap; raising above it needs a paid plan). --memory is the dial; cpu derives from it unless --cpu is given. Billing is actual usage — the ceiling caps what the database may burn, it is not a price`)
|
|
347
|
+
.option('--memory <size>', 'memory ceiling, e.g. 512mb or 1gb').option('--cpu <n>', 'vCPU ceiling override (provider sizes: 1, 2, 4, 6, 8)')
|
|
348
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
349
|
+
.action(guard((service, o) => computeCmd.serviceLimits(type, service, o)));
|
|
350
|
+
g.command('volume [service]').description(`Show, attach, or grow a ${type} service's data volume (the image's data directory). No flag: size and the plan cap (any plan). --size on a volumeless service ATTACHES one (any plan up to its own plan cap; a size above the free cap is paid); on a volume-bearing one it grows (paid plans; grow-only once attached — a provisioned disk cannot shrink). A managed database's volume cannot be deleted — remove the service instead. Billing is actual data stored — the size is a cap, not a price`)
|
|
351
|
+
.option('--size <gi>', 'new size in whole Gi, e.g. 10 (must be ≥ the current size)')
|
|
352
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
353
|
+
.action(guard((service, o) => computeCmd.serviceVolume(type, service, o)));
|
|
354
|
+
g.command('always-on <mode> [service]').description(`Set a ${type} service always-on (mode: on|off). on = machines never scale to zero; off = scale-to-zero. Billing is actual usage either way`)
|
|
355
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
356
|
+
.action(guard((mode, service, o) => computeCmd.serviceAlwaysOn(type, mode, service, o)));
|
|
357
|
+
addObservability(g, type, type);
|
|
358
|
+
}
|
|
359
|
+
// ---- storage (bucket objects + access mode) ----
|
|
360
|
+
const storage = program.command('storage').description("Storage services: browse, download, delete bucket objects; set the bucket's access mode");
|
|
317
361
|
storage.command('list').description("List the bucket's objects. S3 filters by prefix only — there is no substring search")
|
|
318
362
|
.option('--prefix <p>', 'only keys starting with this prefix (applied server-side)')
|
|
319
363
|
.option('--cursor <c>', 'continue from the nextCursor a previous page printed')
|
|
@@ -331,6 +375,36 @@ storage.command('delete <key>').description('DELETES one object from the bucket
|
|
|
331
375
|
.option('--service <name>', 'storage service (default: the sole one on the branch)')
|
|
332
376
|
.option('--branch <b>', 'branch (default: current)').option('--json')
|
|
333
377
|
.action(guard((key, o) => storageCmd.storageDelete(key, o)));
|
|
378
|
+
storage.command('set-access <access>').description("Set the bucket's access mode — public (anonymous public-read) or private (the default)")
|
|
379
|
+
.option('--service <name>', 'storage service (default: the sole one on the branch)')
|
|
380
|
+
.option('--branch <b>', 'branch (default: current)').option('--json')
|
|
381
|
+
.action(guard((access, o) => storageCmd.storageSetAccess(access, o)));
|
|
382
|
+
// ---- build (pre-push verification — local, offline, deploys nothing) ----
|
|
383
|
+
const buildCmd = program.command('build [dir]').description('Verify a source directory would build before deploying: detection plan + the Dockerfile (yours, or the one nixpacks would generate server-side) + static checks. Local and offline — no login needed, nothing pushed. Exit 1 when the verdict is failed')
|
|
384
|
+
.option('--explain', 'include the Dockerfile content in the output')
|
|
385
|
+
.option('--port <p>', 'port the app listens on (else the Dockerfile EXPOSE)')
|
|
386
|
+
.option('--json')
|
|
387
|
+
.action(guard((dir, o) => build(dir, o)));
|
|
388
|
+
// commander 12 defaults allowExcessArguments to true, so a mistyped subcommand (e.g. `build lgs`)
|
|
389
|
+
// would otherwise be taken as the [dir] positional and run this group's own action instead of failing.
|
|
390
|
+
buildCmd.allowExcessArguments(false);
|
|
391
|
+
buildCmd.command('logs <build-id>').description('Read source-build output for a deploy operation or GitHub build')
|
|
392
|
+
.option('--source <source>', 'archive or github', 'archive').option('--follow', 'poll new output until the build ends').option('--json')
|
|
393
|
+
.action(guard((id, opts) => buildLogs(id, opts)));
|
|
394
|
+
// ---- deploy ----
|
|
395
|
+
program.command('deploy [dir]').description('Deploy a source directory (built remotely; on insta-compute a Dockerfile is optional and nixpacks detects the runtime) or a prebuilt --image to a branch compute group')
|
|
396
|
+
.option('--image <url>', 'prebuilt container image to deploy (instead of a source dir)').option('--branch <b>').option('--group <g>').option('--port <p>')
|
|
397
|
+
.option('--websocket', 'run a WebSocket app (larger guest + connection-based concurrency)')
|
|
398
|
+
.option('--replace-source', 'the service deploys from a connected GitHub repo: switch it to this image and remove the repo connection (admin); without it such a deploy is refused')
|
|
399
|
+
.option('--json', 'print the deploy result as JSON (build progress goes to stderr)')
|
|
400
|
+
.action(guard((dir, o) => deploy(dir, o)));
|
|
401
|
+
// ---- run (per-request secret injection — nothing written to disk) ----
|
|
402
|
+
program.command('run <cmd> [args...]').description('Run a command with the branch credential bundle injected into its environment (no .env written)')
|
|
403
|
+
.option('--branch <b>', 'branch bundle to inject (default: linked branch)')
|
|
404
|
+
.option('--service <type/name>', "inject one compute service's own slice of the branch bundle, e.g. compute/api — the unambiguous read when several services define the same name (NOT the container's env: it also carries the branch's provider credentials, which a container gets only where bound)")
|
|
405
|
+
.option('--ignore-collisions', 'run even when several services define the same name; every such name is REMOVED from the child environment (never inherited from your shell)')
|
|
406
|
+
.passThroughOptions().allowUnknownOption()
|
|
407
|
+
.action(guard((cmd, args, o) => runCmd.run([cmd, ...(args ?? [])], o)));
|
|
334
408
|
// ---- templates (registry, local insta.template.yaml, or a GitHub URL) ----
|
|
335
409
|
const tpl = program.command('template').description('Browse and deploy app templates (registry, a local dir, or a GitHub URL)');
|
|
336
410
|
tpl.command('list').description('List templates in the platform registry').option('--json').action(guard((o) => template.templateList(o)));
|
|
@@ -338,88 +412,43 @@ tpl.command('info <code>').description('Show a template: version, upstream pin,
|
|
|
338
412
|
.option('--json').action(guard((code, o) => template.templateInfo(code, o)));
|
|
339
413
|
tpl.command('deploy <code-or-dir-or-url>').description('Deploy a template onto a branch — a registry code, a local directory containing insta.template.yaml (a path-looking target is always read as a directory), or a github.com URL (https://github.com/<owner>/<repo>[/tree/<ref>[/<dir>]]) whose manifest is fetched with your own git credentials. Missing required variables are prompted for on a terminal; generator-backed (secret:N) and defaulted ones are resolved by the platform')
|
|
340
414
|
.option('--branch <b>', 'target branch (default: current)')
|
|
341
|
-
.option('--region <region>', 'region for every service the template creates, e.g. us-east (see `insta regions`)')
|
|
415
|
+
.option('--region <region>', 'region for every service the template creates, e.g. us-east (see `insta config regions`)')
|
|
342
416
|
.option('--set <NAME=value>', 'set a template variable (repeatable)', (v, prev) => [...prev, v], [])
|
|
343
417
|
.option('-y, --yes', 'non-interactive: missing required variables fail with a --set list instead of prompting')
|
|
344
418
|
.option('--json')
|
|
345
419
|
.action(guard((target, o) => template.templateDeploy(target, o)));
|
|
346
|
-
// ----
|
|
347
|
-
program.command('
|
|
348
|
-
// ---- regions ----
|
|
349
|
-
program.command('regions').description('List regions available for postgres/compute services').option('--json').action(guard((o) => regions.regionsList(o)));
|
|
350
|
-
// ---- observability ----
|
|
351
|
-
program.command('build-logs <build-id>').description('Read source-build output for a deploy operation or GitHub build')
|
|
352
|
-
.option('--source <source>', 'archive or github', 'archive').option('--follow', 'poll new output until the build ends').option('--json')
|
|
353
|
-
.action(guard((id, opts) => buildLogs(id, opts)));
|
|
354
|
-
program.command('metrics <target> [group]').description('Service metrics (target: db|compute|redis|mysql|mongodb)')
|
|
355
|
-
.option('--branch <b>').option('--from <unix>').option('--to <unix>').option('--step <s>').option('--json')
|
|
356
|
-
.action(guard((target, group, o) => obs.metrics(target, group, o)));
|
|
357
|
-
program.command('logs <target> [group]').description('Service logs (runtime by default; --deploy = machine lifecycle events; target: db|compute|redis|mysql|mongodb)')
|
|
358
|
-
.option('--branch <b>').option('--limit <n>').option('--region <r>').option('--instance <i>').option('--deploy', 'show deploy events (machine lifecycle) instead of runtime logs — Fly-backed targets only, not db').option('--json')
|
|
359
|
-
.option('--from <t>', 'window start: unix seconds or ISO-8601 — pages history (~7-day retention); without a window one recent provider page (~100 lines) is returned')
|
|
360
|
-
.option('--to <t>', 'window end: unix seconds or ISO-8601 (default: now)')
|
|
361
|
-
.option('--since <dur>', 'relative window start, e.g. 90s, 30m, 2h, 1d (shorthand for --from now-dur)')
|
|
362
|
-
.action(guard((target, group, o) => obs.logs(target, group, o)));
|
|
363
|
-
program.command('usage').description('Usage for the current billing cycle by billing dimension (org by default; --proj for one project)')
|
|
364
|
-
.option('--from <unix>').option('--to <unix>').option('--proj [id]', 'show one project (the linked one, or a given id) instead of the whole org').option('--json')
|
|
365
|
-
.action(guard((o) => obs.usage(o)));
|
|
366
|
-
// ---- domains bought through InstaCloud (BYO domains: `insta compute set-domain`) ----
|
|
367
|
-
const dom = program.command('domain').description('Buy a domain through InstaCloud and attach it to a compute service (your own domain: `insta compute set-domain`)');
|
|
368
|
-
dom.command('search <keyword>').description('Search purchasable names with prices (a label like "myapp" or a full name like "myapp.com")')
|
|
369
|
-
.option('--tlds <list>', 'comma-separated TLDs to include').option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
370
|
-
.action(guard((keyword, o) => domainCmd.domainSearch(keyword, o)));
|
|
371
|
-
dom.command('buy <name>').description('Buy a domain — pay at the printed Stripe Checkout link. It serves nothing until you attach it (gated: domain.purchase)')
|
|
372
|
-
.option('--years <n>', 'registration term in years (default 1)')
|
|
373
|
-
.option('--no-open', 'print the checkout URL instead of opening a browser').option('--json')
|
|
374
|
-
.action(guard((name, o) => domainCmd.domainBuy(name, o)));
|
|
375
|
-
dom.command('attach <hostname>').description('Point a bought domain, or any subdomain of one, at a compute service — `abc.com` binds it and its www, `api.abc.com` binds only that (gated: deploy)')
|
|
376
|
-
.option('--branch <b>').option('--group <g>', "compute service (default: the branch's sole compute service)").option('--json')
|
|
377
|
-
.action(guard((hostname, o) => domainCmd.domainAttach(hostname, o)));
|
|
378
|
-
dom.command('list').description("Domains bought through InstaCloud in this org — a domain belongs to the org, each of its hostnames to a service")
|
|
379
|
-
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
380
|
-
.action(guard((o) => domainCmd.domainList(o)));
|
|
381
|
-
dom.command('status <name>').description("A bought domain's order and attach state")
|
|
382
|
-
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
383
|
-
.action(guard((name, o) => domainCmd.domainStatus(name, o)));
|
|
384
|
-
const rec = dom.command('records').description('DNS records of a bought domain — the zone InstaCloud holds at the registrar');
|
|
385
|
-
rec.command('list <domain>').description('Every record in the zone, managed ones marked')
|
|
386
|
-
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
387
|
-
.action(guard((domain, o) => domainCmd.domainRecordsList(domain, o)));
|
|
388
|
-
rec.command('add <domain> <type> <name> <content>').description('Add a record — type A|AAAA|CNAME|ANAME|MX|TXT|SRV|NS; name "@" for the domain itself, a label like "www", or the full hostname under it')
|
|
389
|
-
.option('--ttl <seconds>', 'time to live in seconds (default 300)').option('--priority <n>', 'MX and SRV only')
|
|
390
|
-
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
391
|
-
.action(guard((domain, type, name, content, o) => domainCmd.domainRecordsAdd(domain, type, name, content, o)));
|
|
392
|
-
rec.command('set <domain> <id>').description('Change a record by its id (from `records list`); fields you omit keep their value')
|
|
393
|
-
.option('--type <t>', 'A|AAAA|CNAME|ANAME|MX|TXT|SRV|NS').option('--name <host>', '"@" for the domain itself, a label like "www", or the full hostname under it').option('--content <value>', 'the answer').option('--ttl <seconds>', 'time to live in seconds').option('--priority <n>', 'MX and SRV only')
|
|
394
|
-
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
395
|
-
.action(guard((domain, id, o) => domainCmd.domainRecordsSet(domain, id, o)));
|
|
396
|
-
rec.command('remove <domain> <id>').description('Remove a record by its id (a record InstaCloud published for a live hostname is refused)')
|
|
397
|
-
.option('--org <id>', "target org (default: linked project's org)").option('--json')
|
|
398
|
-
.action(guard((domain, id, o) => domainCmd.domainRecordsRemove(domain, id, o)));
|
|
399
|
-
const bill = program.command('billing').description('Current billing cycle overview (tier / used / included / overage / credits / forecast + per-dimension & per-project breakdown)')
|
|
420
|
+
// ---- billing ----
|
|
421
|
+
const bill = program.command('billing').description('Billing: current cycle overview (bare), subscribe to a tier, Stripe portal, usage by dimension')
|
|
400
422
|
.option('--org <id>', 'target org (default: linked project\'s org)').option('--json')
|
|
401
423
|
.action(guard((o) => billing(o)));
|
|
402
|
-
|
|
424
|
+
// commander 12 defaults allowExcessArguments to true, so a mistyped/retired subcommand (e.g.
|
|
425
|
+
// `billing upgrade pro`) would otherwise silently run the overview action instead of failing.
|
|
426
|
+
bill.allowExcessArguments(false);
|
|
427
|
+
bill.command('subscribe <tier>').description('Subscribe the org to a paid tier (pro|team) via Stripe Checkout')
|
|
403
428
|
.option('--org <id>').option('--no-open', 'print the URL instead of opening a browser').option('--json')
|
|
404
429
|
.action(guard((tier, o) => billingUpgrade(tier, o)));
|
|
405
430
|
bill.command('portal').description('Open the Stripe Customer Portal (change plan / card / cancel)')
|
|
406
431
|
.option('--org <id>').option('--no-open', 'print the URL instead of opening a browser').option('--json')
|
|
407
432
|
.action(guard((o) => billingPortal(o)));
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
433
|
+
bill.command('usage').description('Usage for the current billing cycle by billing dimension (org by default; --proj for one project)')
|
|
434
|
+
.option('--from <unix>').option('--to <unix>').option('--proj [id]', 'show one project (the linked one, or a given id) instead of the whole org').option('--json')
|
|
435
|
+
.action(guard((o) => obs.usage(o)));
|
|
436
|
+
// `agent setup` and its hidden alias `setup agent` declare their options from ONE list, so a flag
|
|
437
|
+
// added to the canonical command cannot be missing from the one-liner the console prints.
|
|
438
|
+
function withSetupAgentOptions(cmd) {
|
|
439
|
+
return cmd
|
|
440
|
+
.option('-y, --yes', 'non-interactive')
|
|
441
|
+
.option('--env <prod|staging>', 'deployment to set this machine up for (default: prod — switches and persists, like `insta env use`)')
|
|
442
|
+
.option('--mcp-token', 'register Claude Code with a minted insta_ API token instead of OAuth (requires login and token-creation permission)')
|
|
443
|
+
.option('--project <id>', 'also link this directory to an existing project after setup (flows through login first if needed)')
|
|
444
|
+
.option('--create [name]', 'also create a new project and link this directory after setup (default name: this directory; mutually exclusive with --project)')
|
|
445
|
+
.action(guard((o) => setup.setupAgent(o)));
|
|
446
|
+
}
|
|
447
|
+
// ---- agent (this machine's coding agents + the project's agent governance) ----
|
|
448
|
+
const agent = program.command('agent').description('Agents: set up this machine, the project manifest, access policy, approvals (HITL), the local credential audit, the event timeline');
|
|
449
|
+
withSetupAgentOptions(agent.command('setup').description('Install the insta CLI (if missing), the insta skill for all coding agents, and the MCP server — targets production; pass --env staging for the staging deployment'));
|
|
450
|
+
agent.command('manifest').description('Print an agent-legible view of the project environments').option('--json').action(guard((o) => manifest(o)));
|
|
451
|
+
const agentPol = agent.command('policy').description('Project agent access policy');
|
|
423
452
|
agentPol.command('get').option('--json').action(guard((o) => agentPolicy.get(o)));
|
|
424
453
|
agentPol.command('set <mode>').description('full-access | read-only | branch-specific (resets rules; customize comes from `rule set`)')
|
|
425
454
|
.option('--json').action(guard((mode, o) => agentPolicy.set(mode, o)));
|
|
@@ -429,6 +458,30 @@ agentPol.command('rule').command('set <action> <decision>').description('Set an
|
|
|
429
458
|
.option('--json').action(guard((action, decision, o) => agentPolicy.rule(action, decision, o)));
|
|
430
459
|
agentPol.command('revoke-sessions').description('Revoke ALL CLI agent sessions for this project')
|
|
431
460
|
.option('--json').action(guard((o) => agentPolicy.revoke(o)));
|
|
461
|
+
const ap = agent.command('approvals').description('Governance approvals (HITL)');
|
|
462
|
+
ap.command('list').option('--status <s>', 'pending|granted|denied|consumed').option('--json').action(guard((o) => govern.approvalsList(o)));
|
|
463
|
+
ap.command('approve <id>').option('--json').action(guard((id, o) => govern.approvalsApprove(id, o)));
|
|
464
|
+
ap.command('deny <id>').option('--json').action(guard((id, o) => govern.approvalsDeny(id, o)));
|
|
465
|
+
const ob = agent.command('observe').description('Local credential-audit hook');
|
|
466
|
+
ob.command('install').description('Install the PostToolUse hook into this project').action(guard(() => observe.observeInstall()));
|
|
467
|
+
ob.command('uninstall').action(guard(() => observe.observeUninstall()));
|
|
468
|
+
ob.command('report').description('Render the local credential audit').option('--json').action(guard((o) => observe.observeReport(o)));
|
|
469
|
+
ob.command('sync').description('Upload findings into the project timeline').action(guard(() => observe.observeSync()));
|
|
470
|
+
agent.command('events').description('Show the audit + agent-event timeline').option('--branch <b>').option('--limit <n>').option('--json').action(guard((o) => govern.events(o)));
|
|
471
|
+
// ---- config (this machine's CLI configuration) ----
|
|
472
|
+
const cfg = program.command('config').description('CLI configuration: register the remote MCP server with coding agents, list regions, auto-update');
|
|
473
|
+
cfg.command('install-mcp').description('Register the remote MCP server with coding agents (default: Claude Code + all detected)')
|
|
474
|
+
.option('--agent <slug>', 'one agent: claude-code, cursor, codex, opencode, copilot, factory-droid')
|
|
475
|
+
.option('--mcp-token', 'claude-code only: minted insta_ API token instead of OAuth (requires login and token-creation permission)')
|
|
476
|
+
.action(guard((o) => mcp.mcpInstall(o)));
|
|
477
|
+
cfg.command('regions').description('List regions available for postgres/compute services').option('--json').action(guard((o) => regions.regionsList(o)));
|
|
478
|
+
cfg.command('autoupdate [mode]').description('Show or set auto-update: on | off (default: on while pre-1.0)').action(guard((mode) => selfUpdate.autoupdate(mode)));
|
|
479
|
+
// ---- setup (hidden compatibility alias of `agent setup`) ----
|
|
480
|
+
// `npx -y insta@latest setup agent [--project <id>]` is printed by the console, the landing page
|
|
481
|
+
// and third-party docs; it must keep working on every release. Permanent, like `services|svc`;
|
|
482
|
+
// hidden so the canonical `agent setup` is the only one help advertises.
|
|
483
|
+
const setupCompat = program.command('setup', { hidden: true }).description('Compatibility alias: `insta setup agent` is `insta agent setup`');
|
|
484
|
+
withSetupAgentOptions(setupCompat.command('agent').description('Alias of `insta agent setup`, kept for the console one-liner'));
|
|
432
485
|
// ---- feedback (agent + human hurdle reports → the InstaCloud team) ----
|
|
433
486
|
program.command('feedback')
|
|
434
487
|
.description('Report an InstaCloud-side hurdle (bug / missing feature / friction) to the InstaCloud team — about the insta toolkit itself, NEVER about the app you are building. Works logged-out and unlinked.')
|
|
@@ -449,8 +502,6 @@ program.command('feedback')
|
|
|
449
502
|
// ---- self-update ----
|
|
450
503
|
program.command('upgrade').description('Update the insta CLI to the latest release (binary or npm install)')
|
|
451
504
|
.action(guard(() => selfUpdate.upgrade(cliVersion())));
|
|
452
|
-
program.command('autoupdate [mode]').description('Show or set auto-update: on | off (default: on while pre-1.0)')
|
|
453
|
-
.action(guard((mode) => selfUpdate.autoupdate(mode)));
|
|
454
505
|
program.command('__update-check', { hidden: true }).action(guard(() => selfUpdate.backgroundCheck(cliVersion())));
|
|
455
506
|
// The ssh_config renewal hook. Hidden, and named with the `__` prefix that
|
|
456
507
|
// trackCommand skips, because OpenSSH runs it while PARSING the config on EVERY
|
|
@@ -458,6 +509,21 @@ program.command('__update-check', { hidden: true }).action(guard(() => selfUpdat
|
|
|
458
509
|
// critical path of every ordinary ssh.
|
|
459
510
|
program.command('__ssh-ensure-cert <alias>', { hidden: true })
|
|
460
511
|
.action(guard((alias) => computeCmd.ensureCertForAlias(alias)));
|
|
512
|
+
// `--api-url` reaches every command, hidden from each command's own help (the root documents it
|
|
513
|
+
// once). It must be declared per command: positional-options mode matches the root's options only
|
|
514
|
+
// BEFORE the subcommand name, so `insta compute status --api-url X` is legal only if `status` knows
|
|
515
|
+
// the flag. `login` keeps its own copy (that one persists the URL). `compute exec` is skipped:
|
|
516
|
+
// splitExecArgs reads argv ahead of commander and does not know this option takes a value — for
|
|
517
|
+
// exec, pass it at the root: `insta --api-url X compute exec …` (execCommandIndex skips it there).
|
|
518
|
+
function addApiUrlEverywhere(cmd) {
|
|
519
|
+
for (const sub of cmd.commands) {
|
|
520
|
+
if (!(cmd.name() === 'compute' && sub.name() === 'exec') && !sub.options.some((o) => o.long === '--api-url')) {
|
|
521
|
+
sub.addOption(new Option('--api-url <url>').hideHelp());
|
|
522
|
+
}
|
|
523
|
+
addApiUrlEverywhere(sub);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
addApiUrlEverywhere(program);
|
|
461
527
|
selfUpdate.maybeUpdate(cliVersion(), process.argv);
|
|
462
528
|
program.parseAsync(computeArgv);
|
|
463
529
|
//# sourceMappingURL=index.js.map
|
package/dist/observe/hook.js
CHANGED
|
@@ -40,7 +40,7 @@ async function readStdin() {
|
|
|
40
40
|
}
|
|
41
41
|
// Where findings go. The materialized hook lives at <project root>/.insta/observe/hook.js, so its
|
|
42
42
|
// own entry path names the linked project root — the one directory whose .insta/audit.jsonl is
|
|
43
|
-
// gitignored and that `insta observe report` reads. Anything else (the harness's project-dir env,
|
|
43
|
+
// gitignored and that `insta agent observe report` reads. Anything else (the harness's project-dir env,
|
|
44
44
|
// the event cwd) is only a guess: Codex passes the SESSION cwd, which in a monorepo can be a
|
|
45
45
|
// subdirectory of the project, and writing there would leave an unignored audit log behind.
|
|
46
46
|
export function projectRootFor(entry, env, eventCwd) {
|
package/dist/observe/install.js
CHANGED
|
@@ -7,7 +7,7 @@ import { alreadyTracked, ensureGitignore } from '../gitignore.js';
|
|
|
7
7
|
const MARKER = 'insta-observe';
|
|
8
8
|
// What the hook leaves under ./.insta that is machine-local, not project source: observe/ is a
|
|
9
9
|
// copy of this CLI version's hook + scanner (regenerated by every `project link`), and
|
|
10
|
-
// audit.jsonl is this machine's findings (fingerprints + redacted context — `insta observe sync`
|
|
10
|
+
// audit.jsonl is this machine's findings (fingerprints + redacted context — `insta agent observe sync`
|
|
11
11
|
// is the share path). ./.insta/project.json stays committable: it is the team's project binding.
|
|
12
12
|
const LOCAL_PATHS = ['.insta/observe/', '.insta/audit.jsonl'];
|
|
13
13
|
const GITIGNORE_COMMENT = '# InstaCloud: local observe-hook state (regenerated per machine, not source)';
|