insta 0.0.37 → 0.0.38

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 CHANGED
@@ -29,13 +29,21 @@ For coding agents. Installs the CLI, the `insta` skill for every agent on the ma
29
29
  registers the MCP server — one command for macOS, Linux, WSL, and native Windows shells
30
30
  (PowerShell/cmd). Needs Node 18+ with a writable npm global prefix (a Node version manager
31
31
  qualifies; if the global install can't write, setup continues and prints the exact
32
- version-pinned `npm install -g` fallback to run yourself). E2e-validated on macOS/Linux; the
33
- Windows spawn paths are unit-tested:
32
+ version-pinned `npm install -g` fallback to run yourself). E2e-validated on macOS, Linux,
33
+ and Windows (PowerShell + cmd):
34
34
 
35
35
  ```bash
36
36
  npx -y insta setup agent
37
37
  ```
38
38
 
39
+ This command means **production** (CLI ≥ 0.0.38): if the machine was previously switched to
40
+ staging it switches back — announced, session dropped, like `insta env use prod`. Staging is
41
+ its own explicit command, which also persists the choice:
42
+
43
+ ```bash
44
+ npx -y insta setup agent --env staging
45
+ ```
46
+
39
47
  On macOS/Linux without Node, the native-binary installer puts the `insta` CLI on PATH (the
40
48
  skill + MCP steps it then runs still need Node — the skills tool runs via npx). Never run it
41
49
  on native Windows — PowerShell's `curl` alias and the WSL `bash` shim break it; use npx
@@ -179,7 +187,7 @@ build never reaches a production installer.
179
187
  |---|---|
180
188
  | `insta login` · `logout` · `status` | Email/password or `--oauth github\|google`; `status` shows the environment, login and linked project/branch |
181
189
  | `insta env` | `show` · `use <prod\|staging>` |
182
- | `insta setup` | `agent` — install the CLI (if missing), the skill, and MCP for every coding agent |
190
+ | `insta setup` | `agent` — install the CLI (if missing), the skill, and MCP for every coding agent; targets prod, `--env staging` for staging |
183
191
  | `insta mcp` | `install` — register the remote MCP server only |
184
192
  | `insta org` | `list` · `create` (one free org per user) |
185
193
  | `insta project` | `create` · `list` · `link` · `delete` |
@@ -73,8 +73,8 @@ export async function envUse(name, opts = {}) {
73
73
  info(' previous session dropped (separate deployment) — run `insta login --oauth github`');
74
74
  // Switching the CLI does NOT re-point already-installed agents: their MCP registration and skill
75
75
  // files were written for the previous environment and are keyed by a different server name, so
76
- // they keep talking to it until setup is re-run. (The installer path is fine — install.sh runs
77
- // `env use` before `setup agent`.)
78
- info(' re-point this machine\'s agents at it with: insta setup agent');
76
+ // they keep talking to it until setup is re-run. --env is REQUIRED in the hint: since 0.0.38 a
77
+ // bare `setup agent` forces prod, which would silently undo the switch the user just made.
78
+ info(` re-point this machine's agents at it with: insta setup agent --env ${target}`);
79
79
  }
80
80
  //# sourceMappingURL=env.js.map
@@ -37,7 +37,10 @@ export const LIMITS = {
37
37
  const FEEDBACK_ENDPOINT = process.env.INSTA_FEEDBACK_URL ||
38
38
  'https://feedback.instacloud.com/v1/feedback';
39
39
  const FEEDBACK_INGEST_TOKEN = process.env.INSTA_FEEDBACK_TOKEN || 'insta-feedback-public-v1';
40
- const FEEDBACK_TIMEOUT_MS = 10_000;
40
+ // 15s gives the backend's scale-to-zero cold start room to answer (the ingest service waits out
41
+ // the DB wake and persists, so a report can land after the old 10s deadline gave up on it).
42
+ // An expired deadline is reported as UNCONFIRMED, not failed — the report may well be stored.
43
+ const FEEDBACK_TIMEOUT_MS = 15_000;
41
44
  const MAX_FILE_BYTES = 256 * 1024;
42
45
  function resolveCliVersion() {
43
46
  // Same resolution as index.ts: the standalone binary bakes INSTA_CLI_VERSION via --define;
@@ -178,8 +181,10 @@ export async function submit(payload, fetchImpl) {
178
181
  });
179
182
  }
180
183
  catch (e) {
181
- const timedOut = e instanceof Error && e.name === 'TimeoutError';
182
- return { status: 'error', error: timedOut ? `timed out after ${FEEDBACK_TIMEOUT_MS / 1000}s` : `network error: ${e instanceof Error ? e.message : String(e)}` };
184
+ if (e instanceof Error && e.name === 'TimeoutError') {
185
+ return { status: 'unconfirmed', error: `no response after ${FEEDBACK_TIMEOUT_MS / 1000}s — the report may have been recorded anyway` };
186
+ }
187
+ return { status: 'error', error: `network error: ${e instanceof Error ? e.message : String(e)}` };
183
188
  }
184
189
  let body = {};
185
190
  try {
@@ -211,6 +216,14 @@ export async function feedback(opts, deps = {}) {
211
216
  return;
212
217
  }
213
218
  const result = await submit(payload, deps.fetchImpl ?? fetch);
219
+ if (result.status === 'unconfirmed') {
220
+ // NOT a failure claim: the request was still in flight at the deadline and the server
221
+ // finishes what it started, so saying "not submitted" here would be a false negative.
222
+ if (opts.json)
223
+ return printJson({ status: 'unconfirmed', error: result.error });
224
+ process.stderr.write(`warning: feedback receipt unconfirmed (${result.error}) — continue with your task, do not retry\n`);
225
+ return;
226
+ }
214
227
  if (result.status === 'error') {
215
228
  // Deliberate exit 0: an agent CANNOT fix a down/rate-limited backend, and feedback must never
216
229
  // fail or distract from the task the user actually asked for. Do not retry.
@@ -10,9 +10,10 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
10
10
  import { dirname, join } from 'node:path';
11
11
  import os from 'node:os';
12
12
  import { ApiClient } from '../api.js';
13
- import { resolveEnv } from '../config.js';
14
- import { DEFAULT_ENV, ENVS, mcpServerName } from '../env.js';
13
+ import { readPersistedGlobal, resolveEnv } from '../config.js';
14
+ import { DEFAULT_ENV, ENVS, ENV_NAMES, envForApiUrl, envFromEnvVar, isEnvName, mcpServerName } from '../env.js';
15
15
  import { info } from '../util.js';
16
+ import { envUse } from './env.js';
16
17
  import { installAgentConfigs } from './mcp.js';
17
18
  import { detectChannel } from './upgrade.js';
18
19
  // The `skills` tool we shell out to prints a clack UI: a frame-by-frame clone spinner, an
@@ -316,10 +317,49 @@ export async function registerMcp(run = defaultRunner, mint = defaultMinter, use
316
317
  info(` MCP registration failed — add manually:\n claude mcp add --transport http ${name} ${url}`);
317
318
  }
318
319
  }
319
- export async function setupAgent(opts, run = defaultRunner, mint, installConfigs = installAgentConfigs, ensure = (r) => ensureCliInstalled(r)) {
320
+ /** The environment `setup agent` should target, and whether the machine must be switched to it
321
+ * first. Pure — decides only; the caller performs the switch.
322
+ *
323
+ * The contract (CLI ≥ 0.0.38): the public one-liner `npx -y insta setup agent` means PRODUCTION,
324
+ * full stop — a leftover `insta env use staging` from last month must not silently give a new
325
+ * onboarding run staging skills. Staging is an explicit ask: `--env staging` (or $INSTA_ENV).
326
+ * Two deliberate exceptions leave the machine alone:
327
+ * - an explicit $INSTA_API_URL (insta-oss, a preview) with no --env — a hand-written URL is the
328
+ * most specific instruction there is;
329
+ * - a persisted CUSTOM apiUrl with no --env — same reasoning, chosen via login --api-url. */
330
+ export function planSetupEnv(flagEnv, persistedApiUrl, apiUrlOverride = process.env.INSTA_API_URL, envVar = envFromEnvVar()) {
331
+ if (flagEnv !== undefined) {
332
+ const want = flagEnv.trim().toLowerCase();
333
+ if (!isEnvName(want))
334
+ throw new Error(`unknown --env "${flagEnv}" — expected one of: ${ENV_NAMES.join(', ')}`);
335
+ // A contradicting ambient override must ERROR, not lose quietly: everything after this plan
336
+ // resolves through resolveEnv(), where $INSTA_API_URL (and $INSTA_ENV) outrank the persisted
337
+ // config — proceeding would persist one environment and install another's skills/MCP.
338
+ if (apiUrlOverride)
339
+ throw new Error(`--env ${want} conflicts with $INSTA_API_URL=${apiUrlOverride} — unset one`);
340
+ if (envVar && envVar !== want)
341
+ throw new Error(`--env ${want} conflicts with $INSTA_ENV=${envVar} — unset one`);
342
+ return { target: want, switch: envForApiUrl(persistedApiUrl) !== want };
343
+ }
344
+ if (apiUrlOverride)
345
+ return { target: null, switch: false };
346
+ const persisted = envForApiUrl(persistedApiUrl);
347
+ if (persisted === null)
348
+ return { target: null, switch: false }; // custom host, chosen deliberately
349
+ const target = envVar ?? DEFAULT_ENV;
350
+ return { target, switch: persisted !== target };
351
+ }
352
+ export async function setupAgent(opts, run = defaultRunner, mint, installConfigs = installAgentConfigs, ensure = (r) => ensureCliInstalled(r), readStored = readPersistedGlobal, switchEnv = (n) => envUse(n)) {
320
353
  if (!opts.yes && !process.stdout.isTTY) {
321
354
  info('non-interactive shell — assuming -y');
322
355
  }
356
+ // Pin the environment BEFORE anything is installed (see planSetupEnv). A required switch goes
357
+ // through `env use` — the one path that persists the choice and drops the now-foreign session —
358
+ // and announces itself, so the machine can never end up with its CLI on one deployment and its
359
+ // skills/MCP on another.
360
+ const plan = planSetupEnv(opts.env, (await readStored()).apiUrl);
361
+ if (plan.switch && plan.target)
362
+ await switchEnv(plan.target);
323
363
  // BEFORE the skill install: the skill tells agents to run `insta …`, so a durable CLI must
324
364
  // exist by the time it lands.
325
365
  await ensure(run);
@@ -351,5 +391,14 @@ export async function setupAgent(opts, run = defaultRunner, mint, installConfigs
351
391
  const others = await installConfigs();
352
392
  if (others.length)
353
393
  info(`✓ MCP — also configured for ${others.join(', ')} (restart those tools to pick it up)`);
394
+ // The checkmarks end the install, but the user's next move shouldn't be guesswork. Local state
395
+ // only (no network): a config with a session means logged in — good enough for a hint.
396
+ // Deliberately prod-hosted prompt.md even on a staging setup: it is generic onboarding (the
397
+ // login/create commands in it are env-aware at runtime), and staging serves no equivalent.
398
+ const stored = await readStored();
399
+ const loggedIn = !!(stored.accessToken || stored.user);
400
+ info(loggedIn
401
+ ? 'next: `insta project create <name>` in your app repo — or tell your agent: "Fetch https://instacloud.com/prompt.md and follow it"'
402
+ : 'next: `insta login --oauth github` (headless: `insta login --device`), then `insta project create <name>` — or tell your agent: "Fetch https://instacloud.com/prompt.md and follow it"');
354
403
  }
355
404
  //# sourceMappingURL=setup.js.map
package/dist/index.js CHANGED
@@ -82,8 +82,9 @@ program.command('run <cmd> [args...]').description('Run a command with the branc
82
82
  .action(guard((cmd, args, o) => runCmd.run([cmd, ...(args ?? [])], o)));
83
83
  // ---- agent setup (the `curl … | sh --agents` target) ----
84
84
  const setupCmd = program.command('setup').description('Set up this machine for InstaCloud agent workflows');
85
- setupCmd.command('agent').description('Install the insta CLI (if missing), the insta skill for all coding agents, and the MCP server')
85
+ 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')
86
86
  .option('-y, --yes', 'non-interactive')
87
+ .option('--env <prod|staging>', 'deployment to set this machine up for (default: prod — switches and persists, like `insta env use`)')
87
88
  .option('--mcp-token', 'register the MCP server with a minted insta_ API token instead of OAuth (headless machines / CI)')
88
89
  .action(guard((o) => setup.setupAgent(o)));
89
90
  // ---- MCP server integration ----
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insta",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "type": "module",
5
5
  "description": "InstaCloud CLI — a thin client of the platform control-plane API.",
6
6
  "keywords": [