insta 0.0.5 → 0.0.8

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.
@@ -1,26 +1,51 @@
1
1
  import { ApiClient, requireProject } from '../api.js';
2
2
  import { die, info, openUrl, printJson } from '../util.js';
3
+ import { cycleLine, dimensionLines } from './metrics.js';
3
4
  // Resolve the target org: explicit --org, else the linked project's org.
4
5
  async function resolveOrgId(opts) {
5
6
  if (opts.org)
6
7
  return opts.org;
7
8
  return (await requireProject()).orgId;
8
9
  }
9
- // insta billing — current cycle summary for the org.
10
+ // Format the billing overview into printable lines (pure, so it's unit-testable).
11
+ export function billingLines(s) {
12
+ const t = s.totals;
13
+ const lines = [
14
+ `tier: ${s.tier}`,
15
+ `status: ${s.billingStatus}`,
16
+ cycleLine(s.window),
17
+ `included: $${Number(t.includedUsd).toFixed(2)}`,
18
+ `used: $${Number(t.usedUsd).toFixed(4)}`,
19
+ `overage: $${Number(t.overageUsd).toFixed(4)}`,
20
+ `credits: $${Number(t.creditsUsd).toFixed(2)}`,
21
+ `forecast: $${Number(t.forecastUsd).toFixed(4)} (predicted full cycle)`,
22
+ ];
23
+ if (s.subscriptionStatus)
24
+ lines.push(`subscription: ${s.subscriptionStatus}`);
25
+ if (s.billingStatus === 'suspended') {
26
+ lines.push('⚠ org suspended — billing limit reached; resumes next cycle (or `insta billing upgrade pro`)');
27
+ }
28
+ if (s.byDimension?.length) {
29
+ lines.push('by dimension:');
30
+ for (const l of dimensionLines(s.byDimension))
31
+ lines.push(` ${l}`);
32
+ }
33
+ if (s.byProject?.length) {
34
+ lines.push('by project:');
35
+ for (const pr of s.byProject)
36
+ lines.push(` ${pr.name}: $${Number(pr.totalCostUsd ?? 0).toFixed(4)}`);
37
+ }
38
+ return lines;
39
+ }
40
+ // insta billing — current cycle overview for the org (totals, credits, forecast, breakdowns).
10
41
  export async function billing(opts) {
11
42
  const api = await ApiClient.load();
12
43
  const orgId = await resolveOrgId(opts);
13
- const s = await api.request('GET', `/orgs/${orgId}/billing`);
44
+ const s = await api.request('GET', `/orgs/${orgId}/billing/overview`);
14
45
  if (opts.json)
15
46
  return printJson(s);
16
- info(`tier: ${s.tier}`);
17
- info(`status: ${s.status}`);
18
- info(`cycle: ${String(s.cycleStart).slice(0, 10)} → ${String(s.cycleEnd).slice(0, 10)}`);
19
- info(`included: $${Number(s.includedUsd).toFixed(2)}`);
20
- info(`used: $${Number(s.usedUsd).toFixed(4)}`);
21
- info(`overage: $${Number(s.overageUsd).toFixed(4)}`);
22
- if (s.status === 'suspended')
23
- info('⚠ org suspended — billing limit reached; resumes next cycle (or `insta billing upgrade pro`)');
47
+ for (const l of billingLines(s))
48
+ info(l);
24
49
  }
25
50
  // insta billing upgrade <tier> — start a Stripe Checkout to subscribe the org to a paid tier.
26
51
  export async function billingUpgrade(tier, opts) {
@@ -1,8 +1,31 @@
1
1
  import { resolve, join } from 'node:path';
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
3
  import { ApiClient, requireProject } from '../api.js';
4
4
  import { info, die, handleApproval } from '../util.js';
5
5
  import { flyctlBuildAndPush, ensureFlyctl } from '../flyctl-build.js';
6
+ // Map CLI options to the platform deploy request body. Pure, so it's unit-tested. --websocket is only
7
+ // sent when set (plain deploys unchanged).
8
+ export function deployRequestBody(image, branch, opts) {
9
+ return {
10
+ image,
11
+ branch,
12
+ group: opts.group,
13
+ port: opts.port ? Number(opts.port) : undefined,
14
+ websocket: opts.websocket ? true : undefined,
15
+ };
16
+ }
17
+ // A port mismatch is the #1 deploy mistake: the app boots "successfully" but the proxy routes to
18
+ // the wrong internal port and every request is refused. For source deploys the Dockerfile states
19
+ // the truth — use its (last) EXPOSE as the default instead of a blind 8080.
20
+ export function dockerfileExposedPort(dockerfile) {
21
+ let port;
22
+ for (const line of dockerfile.split('\n')) {
23
+ const m = /^\s*EXPOSE\s+(\d+)/i.exec(line);
24
+ if (m)
25
+ port = Number(m[1]);
26
+ }
27
+ return port;
28
+ }
6
29
  // Deploy either a prebuilt image (`--image`) or a source directory (positional `<dir>`, built
7
30
  // remotely on Fly and pushed with a short-lived platform-minted token). Exactly one mode.
8
31
  export async function deploy(dir, opts) {
@@ -13,13 +36,18 @@ export async function deploy(dir, opts) {
13
36
  const api = await ApiClient.load();
14
37
  const p = await requireProject();
15
38
  const branch = opts.branch ?? p.branch;
16
- const image = dir ? await buildFromSource(api, p.projectId, dir, branch, opts) : opts.image;
17
- const res = await api.rawRequest('POST', `/projects/${p.projectId}/deploy`, {
18
- image,
19
- branch,
20
- group: opts.group,
21
- port: opts.port ? Number(opts.port) : undefined,
22
- });
39
+ let port = opts.port ? Number(opts.port) : undefined;
40
+ if (dir && port === undefined) {
41
+ const dockerfile = join(resolve(process.cwd(), dir), 'Dockerfile');
42
+ const exposed = existsSync(dockerfile) ? dockerfileExposedPort(readFileSync(dockerfile, 'utf8')) : undefined;
43
+ if (exposed) {
44
+ port = exposed;
45
+ info(`using port ${exposed} (Dockerfile EXPOSE) — override with --port`);
46
+ }
47
+ }
48
+ const effOpts = { ...opts, port: port?.toString() };
49
+ const image = dir ? await buildFromSource(api, p.projectId, dir, branch, effOpts) : opts.image;
50
+ const res = await api.rawRequest('POST', `/projects/${p.projectId}/deploy`, deployRequestBody(image, branch, effOpts));
23
51
  if (handleApproval(res))
24
52
  return;
25
53
  info(`deployed ${image} -> ${res.body.url} (branch ${res.body.branch}, group ${res.body.group})`);
@@ -28,16 +28,22 @@ export async function metrics(component, group, opts) {
28
28
  const DIMENSION_LABEL = { ram: 'memory' };
29
29
  // Window line. Defaults to the current billing cycle; `to` is the exclusive next-cycle start, so
30
30
  // show the inclusive last day (to − 1 day) — e.g. an org created on the 5th reads "…-05 → …next-04".
31
- function cycleLine(res) {
31
+ export function cycleLine(res) {
32
32
  const day = (sec) => new Date(sec * 1000).toISOString().slice(0, 10);
33
33
  return `billing cycle ${day(res.from)} → ${day(res.to - 86400)}`;
34
34
  }
35
- function printDimensions(dims) {
36
- for (const d of dims) {
35
+ // Pure: format the per-dimension lines (label: qty unit (cost)). Shared by `insta usage` and
36
+ // `insta billing` so both render dimensions identically.
37
+ export function dimensionLines(dims) {
38
+ return dims.map((d) => {
37
39
  const label = DIMENSION_LABEL[d.dimension] ?? d.dimension;
38
40
  const cost = d.costUsd != null ? ` ($${Number(d.costUsd).toFixed(4)})` : '';
39
- info(`${label}: ${d.quantity} ${d.unit}${cost}`);
40
- }
41
+ return `${label}: ${d.quantity} ${d.unit}${cost}`;
42
+ });
43
+ }
44
+ function printDimensions(dims) {
45
+ for (const l of dimensionLines(dims))
46
+ info(l);
41
47
  }
42
48
  // insta usage — usage across the 5 billing dimensions (cpu/memory/volume/egress/storage) for the
43
49
  // current billing cycle. Shows the whole ORG by default (with a per-project breakdown); pass --proj
@@ -0,0 +1,34 @@
1
+ // `insta setup agent` — make this machine's coding agents InstaCloud-native in one step
2
+ // (the Railway `railway setup agent` pattern). Installs the `insta` skill USER-GLOBALLY for
3
+ // every agent the skills tool knows: the skill is pure product knowledge with brand-gated
4
+ // triggers — no project state in it (the project binding is carried by ./.insta/project.json
5
+ // at command time), so one machine-level copy is strictly better than per-project copies.
6
+ // Stack skills (neon/tigris/better-auth) intentionally stay per-project: their presence in a
7
+ // project doubles as its stack manifest — that install happens on `project create|link`.
8
+ import { spawn } from 'node:child_process';
9
+ import { info } from '../util.js';
10
+ const defaultRunner = (cmd, args) => new Promise((resolve) => {
11
+ const env = { ...process.env, AI_AGENT: process.env.AI_AGENT || 'insta' };
12
+ const p = spawn(cmd, args, { stdio: 'inherit', env });
13
+ p.on('error', () => resolve({ ok: false }));
14
+ p.on('close', (code) => resolve({ ok: code === 0 }));
15
+ });
16
+ // -g = user-level (machine-global); -a '*' = every agent dir the skills tool supports
17
+ // (Claude Code, Codex, Cursor, OpenCode, Copilot, …); --copy = real files, not cache symlinks.
18
+ export const SETUP_ARGS = ['skills', 'add', 'InsForge/insta-skills', '-s', 'insta', '-a', '*', '-g', '-y', '--copy'];
19
+ export async function setupAgent(opts, run = defaultRunner) {
20
+ if (!opts.yes && !process.stdout.isTTY) {
21
+ info('non-interactive shell — assuming -y');
22
+ }
23
+ info('installing the insta skill (user-global, all agents) …');
24
+ const res = await run('npx', SETUP_ARGS);
25
+ if (!res.ok) {
26
+ info(' skill install failed — install manually with:');
27
+ info(' npx skills add InsForge/insta-skills -s insta -a "*" -g -y --copy');
28
+ process.exitCode = 1;
29
+ return;
30
+ }
31
+ info('done — agents on this machine now know InstaCloud.');
32
+ info('next: `insta login --oauth github` (cloud) or run instad locally (insta-oss), then `insta project create <name>`.');
33
+ }
34
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,151 @@
1
+ // Self-update: `insta upgrade` updates the CLI in place, channel-aware (native binary via the
2
+ // release installer; npm via `npm i -g`). A background version check (detached, cached 24h in
3
+ // ~/.insta/update-check.json) powers an update nudge — and, since the CLI is young and moves
4
+ // fast, AUTO-UPDATE IS ON BY DEFAULT: when a newer version is known, the next invocation spawns
5
+ // a quiet detached upgrade. `insta autoupdate off` (or INSTA_NO_AUTOUPDATE=1) disables that,
6
+ // leaving just the stderr nudge.
7
+ import { spawn } from 'node:child_process';
8
+ import { dirname, join } from 'node:path';
9
+ import { homedir } from 'node:os';
10
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
11
+ import { readGlobal, writeGlobal } from '../config.js';
12
+ import { info } from '../util.js';
13
+ const INSTALL_SH = 'https://raw.githubusercontent.com/InsForge/insta-cli/main/install.sh';
14
+ const REGISTRY_LATEST = 'https://registry.npmjs.org/insta/latest';
15
+ const CHECK_TTL_MS = 24 * 60 * 60 * 1000; // re-check the registry at most once a day
16
+ const AUTO_THROTTLE_MS = 60 * 60 * 1000; // don't respawn a failed auto-upgrade more than hourly
17
+ const cachePath = () => process.env.INSTA_UPDATE_CACHE ?? join(homedir(), '.insta', 'update-check.json');
18
+ // How is this CLI running? Bun standalone → execPath IS the insta binary (not node);
19
+ // npm global → the module lives under node_modules; anything else is a source checkout.
20
+ export function detectChannel(execPath = process.execPath, moduleUrl = import.meta.url) {
21
+ if (!/node(\.exe)?$/.test(execPath))
22
+ return 'binary';
23
+ if (moduleUrl.includes('/node_modules/'))
24
+ return 'npm';
25
+ return 'source';
26
+ }
27
+ // -1 / 0 / 1 for a<b / a==b / a>b on dotted numeric versions ("0.0.4" style).
28
+ export function cmpSemver(a, b) {
29
+ const pa = a.split('.').map((n) => parseInt(n, 10) || 0);
30
+ const pb = b.split('.').map((n) => parseInt(n, 10) || 0);
31
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
32
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
33
+ if (d !== 0)
34
+ return d < 0 ? -1 : 1;
35
+ }
36
+ return 0;
37
+ }
38
+ export function readCache() {
39
+ try {
40
+ return JSON.parse(readFileSync(cachePath(), 'utf8'));
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ export function writeCache(c) {
47
+ mkdirSync(dirname(cachePath()), { recursive: true });
48
+ writeFileSync(cachePath(), JSON.stringify(c));
49
+ }
50
+ // Pure decision for what start-up should do given the cache. Exported for tests.
51
+ export function decideAction(cache, current, autoUpdate, channel, now = Date.now()) {
52
+ if (!cache || cmpSemver(cache.latest, current) <= 0)
53
+ return 'none';
54
+ if (!autoUpdate || channel === 'source')
55
+ return 'nudge';
56
+ if (cache.lastAutoAt && now - cache.lastAutoAt < AUTO_THROTTLE_MS)
57
+ return 'nudge';
58
+ return 'auto';
59
+ }
60
+ // `insta upgrade` — synchronous, visible self-update on the detected channel.
61
+ export async function upgrade() {
62
+ const channel = detectChannel();
63
+ if (channel === 'source') {
64
+ info('running from a source checkout — `git pull` to update');
65
+ return;
66
+ }
67
+ info(`upgrading insta via ${channel} …`);
68
+ let cmd;
69
+ let args;
70
+ let env = process.env;
71
+ if (channel === 'binary') {
72
+ cmd = 'sh';
73
+ args = ['-c', `curl -fsSL ${INSTALL_SH} | sh`];
74
+ env = { ...process.env, INSTA_INSTALL_DIR: dirname(process.execPath) };
75
+ }
76
+ else {
77
+ cmd = 'npm';
78
+ args = ['install', '-g', 'insta@latest'];
79
+ }
80
+ await new Promise((resolve, reject) => {
81
+ const p = spawn(cmd, args, { stdio: 'inherit', env });
82
+ p.on('error', reject);
83
+ p.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`upgrade failed (exit ${code})`))));
84
+ });
85
+ }
86
+ // Hidden `insta __update-check` — runs detached in the background: fetch latest, refresh cache.
87
+ export async function backgroundCheck() {
88
+ try {
89
+ const ctl = new AbortController();
90
+ const t = setTimeout(() => ctl.abort(), 5000);
91
+ const res = await fetch(REGISTRY_LATEST, { signal: ctl.signal });
92
+ clearTimeout(t);
93
+ if (!res.ok)
94
+ return;
95
+ const { version } = (await res.json());
96
+ if (version)
97
+ writeCache({ ...(readCache() ?? { checkedAt: 0, latest: '0.0.0' }), checkedAt: Date.now(), latest: version });
98
+ }
99
+ catch { /* offline / registry down — try again next TTL */ }
100
+ }
101
+ // `insta autoupdate [on|off]` — toggle / show the auto-update preference (default: on).
102
+ export async function autoupdate(mode) {
103
+ const cfg = await readGlobal();
104
+ if (mode === 'on' || mode === 'off') {
105
+ await writeGlobal({ ...cfg, autoUpdate: mode === 'on' });
106
+ info(`autoupdate ${mode}`);
107
+ return;
108
+ }
109
+ const enabled = cfg.autoUpdate !== false && !process.env.INSTA_NO_AUTOUPDATE;
110
+ info(`autoupdate: ${enabled ? 'on' : 'off'} (default on while the CLI is pre-1.0 — \`insta autoupdate off\` to disable)`);
111
+ }
112
+ // Called once at CLI start-up. Never blocks: reads the cache synchronously, prints at most one
113
+ // stderr line, and (when due) spawns detached children for the registry check / quiet upgrade.
114
+ export function maybeUpdate(current, argv) {
115
+ const cmd = argv[2];
116
+ if (cmd === 'upgrade' || cmd === 'autoupdate' || cmd === '__update-check')
117
+ return;
118
+ const channel = detectChannel();
119
+ const cache = readCache();
120
+ // keep the cache fresh (detached; survives this process exiting)
121
+ if (!cache || Date.now() - cache.checkedAt > CHECK_TTL_MS) {
122
+ respawnDetached(['__update-check']);
123
+ }
124
+ let auto = !process.env.INSTA_NO_AUTOUPDATE;
125
+ try { // config read is async elsewhere; a tiny sync read keeps start-up non-blocking
126
+ const raw = JSON.parse(readFileSync(join(homedir(), '.insta', 'config.json'), 'utf8'));
127
+ if (raw.autoUpdate === false)
128
+ auto = false;
129
+ }
130
+ catch { /* no config yet */ }
131
+ const action = decideAction(cache, current, auto, channel);
132
+ if (action === 'nudge') {
133
+ console.error(`↑ insta ${cache.latest} is available (you have ${current}) — run \`insta upgrade\``);
134
+ }
135
+ else if (action === 'auto') {
136
+ writeCache({ ...cache, lastAutoAt: Date.now() });
137
+ respawnDetached(['upgrade']);
138
+ console.error(`↑ auto-updating insta ${current} → ${cache.latest} in the background (\`insta autoupdate off\` to disable)`);
139
+ }
140
+ }
141
+ // Re-invoke this same CLI (binary or node+script) detached, output discarded.
142
+ function respawnDetached(args) {
143
+ try {
144
+ const script = process.argv[1];
145
+ const argv = /node(\.exe)?$/.test(process.execPath) && script ? [script, ...args] : args;
146
+ const p = spawn(process.execPath, argv, { detached: true, stdio: 'ignore' });
147
+ p.unref();
148
+ }
149
+ catch { /* best-effort */ }
150
+ }
151
+ //# sourceMappingURL=upgrade.js.map
package/dist/config.js CHANGED
@@ -6,14 +6,17 @@ const GLOBAL_DIR = join(homedir(), '.insta');
6
6
  const GLOBAL_FILE = join(GLOBAL_DIR, 'config.json');
7
7
  const PROJECT_DIR = '.insta';
8
8
  const PROJECT_FILE = 'project.json';
9
- const DEFAULT_API = process.env.INSTA_API_URL ?? 'https://beta-api.insta.insforge.dev';
9
+ const DEFAULT_API = 'https://beta-api.insta.insforge.dev';
10
10
  export async function readGlobal() {
11
+ // INSTA_API_URL overrides the persisted apiUrl, not just the default — otherwise the
12
+ // env var is silently ignored as soon as any login has written a config file.
13
+ const envApi = process.env.INSTA_API_URL;
11
14
  try {
12
15
  const parsed = JSON.parse(await readFile(GLOBAL_FILE, 'utf8'));
13
- return { ...parsed, apiUrl: parsed.apiUrl ?? DEFAULT_API };
16
+ return { ...parsed, apiUrl: envApi ?? parsed.apiUrl ?? DEFAULT_API };
14
17
  }
15
18
  catch {
16
- return { apiUrl: DEFAULT_API };
19
+ return { apiUrl: envApi ?? DEFAULT_API };
17
20
  }
18
21
  }
19
22
  export async function writeGlobal(c) {
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { Command } from 'commander';
4
4
  import { ApiError } from './api.js';
5
5
  import { die } from './util.js';
6
6
  import * as auth from './commands/auth.js';
7
+ import * as setup from './commands/setup.js';
7
8
  import * as org from './commands/org.js';
8
9
  import * as project from './commands/project.js';
9
10
  import * as branch from './commands/branch.js';
@@ -16,6 +17,7 @@ import * as govern from './commands/govern.js';
16
17
  import * as observe from './commands/observe.js';
17
18
  import * as obs from './commands/metrics.js';
18
19
  import { billing, billingUpgrade, billingPortal } from './commands/billing.js';
20
+ import * as selfUpdate from './commands/upgrade.js';
19
21
  function onError(e) {
20
22
  if (e instanceof ApiError)
21
23
  die(`${e.message} (HTTP ${e.status})`);
@@ -54,6 +56,11 @@ program.command('login').description('Log in with email + password, or --oauth <
54
56
  .action(guard((o) => auth.login(o)));
55
57
  program.command('logout').description('Log out and clear local tokens').action(guard(() => auth.logout()));
56
58
  program.command('status').description('Show login + linked project').option('--json').action(guard((o) => auth.status(o)));
59
+ // ---- agent setup (the `curl … | sh --agents` target) ----
60
+ const setupCmd = program.command('setup').description('Set up this machine for InstaCloud agent workflows');
61
+ setupCmd.command('agent').description('Install the insta skill user-globally for all coding agents')
62
+ .option('-y, --yes', 'non-interactive')
63
+ .action(guard((o) => setup.setupAgent(o)));
57
64
  // ---- org ----
58
65
  const orgCmd = program.command('org').description('Manage organizations');
59
66
  orgCmd.command('list').option('--json').action(guard((o) => org.orgList(o)));
@@ -93,6 +100,7 @@ sec.command('unset <name>').description('Remove a user secret')
93
100
  // ---- deploy ----
94
101
  program.command('deploy [dir]').description('Deploy a source directory (built remotely on Fly) or a prebuilt --image to a branch compute group')
95
102
  .option('--image <url>', 'prebuilt container image to deploy (instead of a source dir)').option('--branch <b>').option('--group <g>').option('--port <p>')
103
+ .option('--websocket', 'run a WebSocket app (larger guest + connection-based concurrency)')
96
104
  .action(guard((dir, o) => deploy(dir, o)));
97
105
  // ---- compute custom domains (bring your own domain → Fly cert + routing) ----
98
106
  const compute = program.command('compute').description('Compute custom domains (bring your own domain)');
@@ -114,7 +122,7 @@ program.command('logs <target> [group]').description('Service runtime logs (targ
114
122
  program.command('usage').description('Usage for the current billing cycle by billing dimension (org by default; --proj for one project)')
115
123
  .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')
116
124
  .action(guard((o) => obs.usage(o)));
117
- const bill = program.command('billing').description('Current billing cycle summary (tier / credit / used / overage)')
125
+ const bill = program.command('billing').description('Current billing cycle overview (tier / used / included / overage / credits / forecast + per-dimension & per-project breakdown)')
118
126
  .option('--org <id>', 'target org (default: linked project\'s org)').option('--json')
119
127
  .action(guard((o) => billing(o)));
120
128
  bill.command('upgrade <tier>').description('Subscribe the org to a paid tier (pro|enterprise) via Stripe Checkout')
@@ -140,5 +148,12 @@ ob.command('sync').description('Upload findings into the project timeline').acti
140
148
  const pol = program.command('policy').description('Governance policy');
141
149
  pol.command('get').option('--json').action(guard((o) => govern.policyGet(o)));
142
150
  pol.command('set <action> <decision>').description('action: secrets.read|secrets.write|deploy|project.delete|branch.delete|service.add|service.remove|service.scale|service.upgrade; decision: allow|deny|approve').action(guard((a, d) => govern.policySet(a, d)));
151
+ // ---- self-update ----
152
+ program.command('upgrade').description('Update the insta CLI to the latest release (binary or npm install)')
153
+ .action(guard(() => selfUpdate.upgrade()));
154
+ program.command('autoupdate [mode]').description('Show or set auto-update: on | off (default: on while pre-1.0)')
155
+ .action(guard((mode) => selfUpdate.autoupdate(mode)));
156
+ program.command('__update-check', { hidden: true }).action(guard(() => selfUpdate.backgroundCheck()));
157
+ selfUpdate.maybeUpdate(resolveVersion(), process.argv);
143
158
  program.parseAsync(process.argv);
144
159
  //# sourceMappingURL=index.js.map
@@ -55,8 +55,11 @@ function materialize(cwd, assetDir) {
55
55
  writeFileSync(join(dest, 'VERSION'), cliVersion() + '\n');
56
56
  }
57
57
  function claudeEntry() {
58
- return { matcher: '*', hooks: [{ type: 'command', command: 'node',
59
- args: ['${CLAUDE_PROJECT_DIR}/.insta/observe/hook.js'], timeout: 15, _insta: MARKER }] };
58
+ // Claude Code executes `command` as ONE shell string with $CLAUDE_PROJECT_DIR in the env —
59
+ // there is no `args` field in its hooks schema, so a ${…} template in args reaches node
60
+ // verbatim and throws MODULE_NOT_FOUND after every tool call.
61
+ return { matcher: '*', hooks: [{ type: 'command',
62
+ command: 'node "$CLAUDE_PROJECT_DIR/.insta/observe/hook.js"', timeout: 15, _insta: MARKER }] };
60
63
  }
61
64
  function codexEntry(cwd) {
62
65
  const abs = join(cwd, '.insta', 'observe', 'hook.js'); // Codex doesn't expand ${CLAUDE_PROJECT_DIR}; use an absolute path
package/package.json CHANGED
@@ -1,17 +1,37 @@
1
1
  {
2
2
  "name": "insta",
3
- "version": "0.0.5",
3
+ "version": "0.0.8",
4
4
  "type": "module",
5
5
  "description": "InstaCloud CLI — a thin client of the platform control-plane API.",
6
- "keywords": ["insta", "insforge", "cli", "backend", "baas", "platform"],
6
+ "keywords": [
7
+ "insta",
8
+ "insforge",
9
+ "cli",
10
+ "backend",
11
+ "baas",
12
+ "platform"
13
+ ],
7
14
  "license": "Apache-2.0",
8
15
  "homepage": "https://github.com/InsForge/insta-cli#readme",
9
- "repository": { "type": "git", "url": "git+https://github.com/InsForge/insta-cli.git" },
10
- "bugs": { "url": "https://github.com/InsForge/insta-cli/issues" },
11
- "bin": { "insta": "dist/index.js" },
12
- "files": ["dist/**/*.js"],
13
- "engines": { "node": ">=18" },
14
- "publishConfig": { "access": "public" },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/InsForge/insta-cli.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/InsForge/insta-cli/issues"
22
+ },
23
+ "bin": {
24
+ "insta": "dist/index.js"
25
+ },
26
+ "files": [
27
+ "dist/**/*.js"
28
+ ],
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
15
35
  "scripts": {
16
36
  "build": "tsc -p tsconfig.json",
17
37
  "typecheck": "tsc -p tsconfig.json --noEmit",