insta 0.0.62 → 0.0.63
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 +10 -5
- package/dist/agent.js +86 -0
- package/dist/api.js +14 -0
- package/dist/commands/agent-policy.js +72 -0
- package/dist/commands/compute.js +2 -0
- package/dist/commands/govern.js +2 -19
- package/dist/commands/project.js +5 -0
- package/dist/commands/setup.js +9 -1
- package/dist/config.js +2 -0
- package/dist/index.js +24 -5
- package/dist/telemetry.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,10 +121,15 @@ only. Provider-minted service credentials (`DATABASE_URL`, `BUCKET_NAME`,
|
|
|
121
121
|
|
|
122
122
|
### Destructive actions can require approval
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
`insta policy get` for
|
|
124
|
+
Agent requests are governed by the project's `agent-policy`; human requests use normal RBAC.
|
|
125
|
+
Where the agent policy says `approve`, the command stops and prints an approval id for a human
|
|
126
|
+
admin to grant with `insta approvals approve <id>`. The agent then retries the unchanged request.
|
|
127
|
+
Run `insta --agent agent-policy get --json` for stored overrides, `defaultRules`, `effectiveRules`,
|
|
128
|
+
`bootstrapRules` and `ruleNotes`. Rules distinguish no affected branches (`project`), unprotected
|
|
129
|
+
branches and protected branches. They describe policy, not authorization: RBAC, session checks,
|
|
130
|
+
actual affected resources and compound actions still apply. An empty override object does not
|
|
131
|
+
mean rules are unavailable. Text output also lists effective rules. The old `policy` command and approval
|
|
132
|
+
`--always` option have been removed.
|
|
128
133
|
|
|
129
134
|
### Agents get the same surface
|
|
130
135
|
|
|
@@ -213,7 +218,7 @@ build never reaches a production installer.
|
|
|
213
218
|
| `insta metrics` · `logs` · `events` | Service metrics; runtime logs (`--deploy` for deploy events); audit timeline |
|
|
214
219
|
| `insta usage` · `billing` | Usage by billing dimension; `billing upgrade` · `billing portal` |
|
|
215
220
|
| `insta approvals` | `list` · `approve` · `deny` |
|
|
216
|
-
| `insta policy` | `get` · `set <action> <decision>` |
|
|
221
|
+
| `insta agent-policy` | `get` · `set <mode>` · `protect-branch` · `unprotect-branch` · `rule set <action> <decision>` · `revoke-sessions` |
|
|
217
222
|
| `insta observe` | `install` · `uninstall` · `report` · `sync` — local credential audit |
|
|
218
223
|
| `insta feedback` | Report an InstaCloud-side hurdle (bug / feature-request / friction) to the team — never for the app you are building; works logged-out |
|
|
219
224
|
| `insta upgrade` · `autoupdate` | Update the CLI; show or set auto-update |
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createHash, generateKeyPairSync, randomUUID, sign } from 'node:crypto';
|
|
2
|
+
import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { findProjectRoot, readProject } from './config.js';
|
|
5
|
+
import { alreadyTracked, ensureGitignore } from './gitignore.js';
|
|
6
|
+
let mode = null;
|
|
7
|
+
export function detectAgent(explicit, env = process.env) {
|
|
8
|
+
const client = env.CODEX_THREAD_ID || env.CODEX_CI === '1' ? 'codex'
|
|
9
|
+
: env.CLAUDECODE === '1' ? 'claude-code'
|
|
10
|
+
: env.CURSOR_AGENT === '1' ? 'cursor' : 'unknown';
|
|
11
|
+
return explicit || client !== 'unknown' ? { source: explicit ? 'cli-explicit' : 'cli-detected', client } : null;
|
|
12
|
+
}
|
|
13
|
+
export function configureAgent(value) { mode = value; }
|
|
14
|
+
export function agentMode() { return mode; }
|
|
15
|
+
const hash = (v) => createHash('sha256').update(v).digest('hex');
|
|
16
|
+
export function canonicalTarget(path) {
|
|
17
|
+
const url = new URL(path, 'https://platform.invalid');
|
|
18
|
+
return url.pathname + url.search;
|
|
19
|
+
}
|
|
20
|
+
const guidance = 'agent session missing, expired, or for another project/environment — run `insta setup agent`';
|
|
21
|
+
export async function issueAgentSession(api, projectId) {
|
|
22
|
+
const pair = generateKeyPairSync('ed25519');
|
|
23
|
+
const client = mode?.client ?? detectAgent(false)?.client ?? 'unknown';
|
|
24
|
+
const out = await api.request('POST', '/agent/sessions', {
|
|
25
|
+
projectId, client, publicKey: pair.publicKey.export({ type: 'spki', format: 'pem' }).toString(),
|
|
26
|
+
});
|
|
27
|
+
return { ...out, client, apiUrl: api.apiUrl.replace(/\/+$/, ''), privateKey: pair.privateKey.export({ type: 'pkcs8', format: 'pem' }).toString() };
|
|
28
|
+
}
|
|
29
|
+
export async function saveAgentSession(session, cwd = process.cwd()) {
|
|
30
|
+
const root = await findProjectRoot(cwd) ?? cwd;
|
|
31
|
+
const rel = '.insta/agent-session.json';
|
|
32
|
+
if (alreadyTracked(root, [rel]).length)
|
|
33
|
+
throw new Error('agent-session.json is tracked by Git; untrack it before running insta setup agent');
|
|
34
|
+
ensureGitignore(root, [rel], '# Local agent credentials');
|
|
35
|
+
const dir = join(root, '.insta');
|
|
36
|
+
await mkdir(dir, { recursive: true });
|
|
37
|
+
const temp = join(dir, `.agent-session-${randomUUID()}.tmp`);
|
|
38
|
+
// Ignore crash leftovers too; temporary files contain the same private material.
|
|
39
|
+
ensureGitignore(root, ['.insta/.agent-session-*.tmp']);
|
|
40
|
+
await writeFile(temp, JSON.stringify(session, null, 2), { mode: 0o600 });
|
|
41
|
+
await rename(temp, join(root, rel));
|
|
42
|
+
await chmod(join(root, rel), 0o600);
|
|
43
|
+
}
|
|
44
|
+
export async function setupProjectAgentSession(api, projectId) {
|
|
45
|
+
const id = projectId ?? (await readProject())?.projectId;
|
|
46
|
+
if (!id)
|
|
47
|
+
return false;
|
|
48
|
+
await saveAgentSession(await issueAgentSession(api, id));
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
export async function loadAgentSession(apiUrl, projectId, cwd = process.cwd()) {
|
|
52
|
+
try {
|
|
53
|
+
const root = await findProjectRoot(cwd) ?? cwd;
|
|
54
|
+
const session = JSON.parse(await readFile(join(root, '.insta/agent-session.json'), 'utf8'));
|
|
55
|
+
if (session.projectId !== projectId || session.apiUrl !== apiUrl.replace(/\/+$/, '') || !session.token || !session.privateKey
|
|
56
|
+
|| !Number.isFinite(Date.parse(session.expiresAt)) || Date.parse(session.expiresAt) <= Date.now())
|
|
57
|
+
throw new Error();
|
|
58
|
+
return session;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new Error(guidance);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export async function agentHeaders(api, method, path, rawBody) {
|
|
65
|
+
if (!mode)
|
|
66
|
+
return {};
|
|
67
|
+
if (canonicalTarget(path) === '/agent/sessions' && method === 'POST')
|
|
68
|
+
return {
|
|
69
|
+
'Insta-Actor-Type': 'agent', 'Insta-Agent-Source': mode.source, 'Insta-Agent-Client': mode.client,
|
|
70
|
+
};
|
|
71
|
+
const target = canonicalTarget(path);
|
|
72
|
+
const match = target.match(/^\/projects\/([^/?]+)/);
|
|
73
|
+
// Account reads/project creation have no project policy yet. Mint a short-lived bootstrap
|
|
74
|
+
// assertion in memory. It cannot access project routes; never downgrade to a human request.
|
|
75
|
+
const session = match ? await loadAgentSession(api.apiUrl, decodeURIComponent(match[1])) : await issueAgentSession(api);
|
|
76
|
+
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
77
|
+
const nonce = randomUUID();
|
|
78
|
+
const proof = [method.toUpperCase(), target, hash(rawBody), session.agentSessionId, timestamp, nonce, mode.source, session.client].join('\n');
|
|
79
|
+
return {
|
|
80
|
+
'Insta-Actor-Type': 'agent', 'Insta-Agent-Session': session.agentSessionId,
|
|
81
|
+
'Insta-Agent-Session-Token': session.token, 'Insta-Agent-Source': mode.source,
|
|
82
|
+
'Insta-Agent-Client': session.client, 'Insta-Agent-Timestamp': timestamp,
|
|
83
|
+
'Insta-Agent-Nonce': nonce, 'Insta-Agent-Signature': sign(null, Buffer.from(proof), session.privateKey).toString('base64url'),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=agent.js.map
|
package/dist/api.js
CHANGED
|
@@ -4,6 +4,7 @@ import { readGlobal, writeGlobal, readProject, writeProject } from './config.js'
|
|
|
4
4
|
import { autoResolveProject, promptChoice } from './resolve-project.js';
|
|
5
5
|
import { die } from './util.js';
|
|
6
6
|
import { USER_AGENT } from './version.js';
|
|
7
|
+
import { agentHeaders, agentMode } from './agent.js';
|
|
7
8
|
export class ApiError extends Error {
|
|
8
9
|
status;
|
|
9
10
|
body;
|
|
@@ -16,6 +17,13 @@ export class ApiError extends Error {
|
|
|
16
17
|
this.name = 'ApiError';
|
|
17
18
|
}
|
|
18
19
|
}
|
|
20
|
+
export class AgentApprovalRequired extends Error {
|
|
21
|
+
body;
|
|
22
|
+
constructor(body) {
|
|
23
|
+
super(body.message ?? `approval required: ${body.approvalId}`);
|
|
24
|
+
this.body = body;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
19
27
|
// Store a durable insta_ key as the credential: set it as the bearer and drop any refresh token (an insta_ key never rotates; a stale one would leak to /auth/refresh on a 401).
|
|
20
28
|
export function storeApiKeyCredential(cfg, token, user) {
|
|
21
29
|
cfg.accessToken = token;
|
|
@@ -53,6 +61,8 @@ export class ApiClient {
|
|
|
53
61
|
// Returns parsed body for status < 400 (incl. 202); throws ApiError otherwise.
|
|
54
62
|
async request(method, path, body, opts = {}) {
|
|
55
63
|
const res = await this.raw(method, path, body, opts.auth ?? true);
|
|
64
|
+
if (agentMode() && res.status === 202 && res.body?.status === 'approval_required')
|
|
65
|
+
throw new AgentApprovalRequired(res.body);
|
|
56
66
|
if (res.status >= 400)
|
|
57
67
|
throw new ApiError(res.status, res.body?.error ?? `HTTP ${res.status}`, res.body);
|
|
58
68
|
return res.body;
|
|
@@ -76,6 +86,8 @@ export class ApiClient {
|
|
|
76
86
|
const headers = { 'Content-Type': 'application/json', 'Insta-Hints': '1', 'User-Agent': USER_AGENT };
|
|
77
87
|
if (auth && this.cfg.accessToken)
|
|
78
88
|
headers.Authorization = `Bearer ${this.cfg.accessToken}`;
|
|
89
|
+
if (auth)
|
|
90
|
+
Object.assign(headers, await agentHeaders(this, method, path, body === undefined ? '' : JSON.stringify(body)));
|
|
79
91
|
const res = await this.fetchImpl(this.apiUrl + path, {
|
|
80
92
|
method,
|
|
81
93
|
headers,
|
|
@@ -113,6 +125,8 @@ export async function requireProject() {
|
|
|
113
125
|
const p = await readProject();
|
|
114
126
|
if (p)
|
|
115
127
|
return p;
|
|
128
|
+
if (agentMode())
|
|
129
|
+
die('agent mode requires a linked project — run `insta setup agent --project <id>`');
|
|
116
130
|
// One command, just works: unlinked ≠ error. Resolve the project (auto when there's one,
|
|
117
131
|
// one-keystroke picker when several) and persist the choice so this happens once per dir.
|
|
118
132
|
const api = await ApiClient.load();
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ApiClient, requireProject } from '../api.js';
|
|
2
|
+
import { handleApproval, info, printJson } from '../util.js';
|
|
3
|
+
async function current() {
|
|
4
|
+
const api = await ApiClient.load();
|
|
5
|
+
const project = await requireProject();
|
|
6
|
+
const path = `/projects/${project.projectId}/agent-policy`;
|
|
7
|
+
const out = await api.request('GET', path);
|
|
8
|
+
return { api, project, path, policy: out.policy, out };
|
|
9
|
+
}
|
|
10
|
+
export function displayPolicy(out, opts, output = { info, printJson }) {
|
|
11
|
+
const { info, printJson } = output;
|
|
12
|
+
const { policy, agentSessionEpoch } = out;
|
|
13
|
+
if (opts.json)
|
|
14
|
+
return printJson(out);
|
|
15
|
+
info(`agent policy: ${policy.mode}\nprotected branches: ${policy.protectedBranchIds.join(', ') || '(none)'}\nsession epoch: ${agentSessionEpoch}`);
|
|
16
|
+
if (out.effectiveRules) {
|
|
17
|
+
for (const [scope, rules] of Object.entries(out.effectiveRules)) {
|
|
18
|
+
info(`\n${scope}:`);
|
|
19
|
+
for (const [action, decision] of Object.entries(rules))
|
|
20
|
+
info(` ${action}: ${decision}`);
|
|
21
|
+
}
|
|
22
|
+
info(`\nbootstrap: project.create = ${out.bootstrapRules?.['project.create'] ?? '(not reported)'}`);
|
|
23
|
+
for (const note of out.ruleNotes ?? [])
|
|
24
|
+
info(note);
|
|
25
|
+
}
|
|
26
|
+
else
|
|
27
|
+
info('This Platform does not expose resolved rules; upgrade Platform to inspect defaults.');
|
|
28
|
+
}
|
|
29
|
+
export async function get(opts) {
|
|
30
|
+
const { out } = await current();
|
|
31
|
+
// Forward the public response, never the internal API client (which holds credentials).
|
|
32
|
+
displayPolicy(out, opts);
|
|
33
|
+
}
|
|
34
|
+
async function update(change, opts) {
|
|
35
|
+
const state = await current();
|
|
36
|
+
await change(state.policy, state);
|
|
37
|
+
const result = await state.api.rawRequest('PUT', state.path, state.policy);
|
|
38
|
+
if (handleApproval(result, opts.json))
|
|
39
|
+
return;
|
|
40
|
+
if (opts.json)
|
|
41
|
+
return printJson(result.body);
|
|
42
|
+
info(`agent policy updated: ${result.body.policy.mode}`);
|
|
43
|
+
}
|
|
44
|
+
export async function set(mode, opts) {
|
|
45
|
+
const normalized = mode.replace(/-/g, '_');
|
|
46
|
+
if (!['full_access', 'read_only', 'branch_developer'].includes(normalized))
|
|
47
|
+
throw new Error('mode must be full-access, read-only, or branch-developer');
|
|
48
|
+
return update(policy => { policy.mode = normalized; }, opts);
|
|
49
|
+
}
|
|
50
|
+
export async function protect(branch, enabled, opts) {
|
|
51
|
+
return update(async (policy, { api, project }) => {
|
|
52
|
+
const { branches } = await api.request('GET', `/projects/${project.projectId}/branches`);
|
|
53
|
+
const found = branches.find((b) => b.id === branch || b.name === branch);
|
|
54
|
+
if (!found)
|
|
55
|
+
throw new Error('branch not found');
|
|
56
|
+
policy.protectedBranchIds = enabled ? [...new Set([...policy.protectedBranchIds, found.id])] : policy.protectedBranchIds.filter((id) => id !== found.id);
|
|
57
|
+
}, opts);
|
|
58
|
+
}
|
|
59
|
+
export async function rule(action, decision, opts) {
|
|
60
|
+
if (!['allow', 'deny', 'approve'].includes(decision))
|
|
61
|
+
throw new Error('decision must be allow, deny, or approve');
|
|
62
|
+
return update(policy => { policy.branchDeveloperRules[action] = decision; }, opts);
|
|
63
|
+
}
|
|
64
|
+
export async function revoke(opts) {
|
|
65
|
+
const api = await ApiClient.load();
|
|
66
|
+
const project = await requireProject();
|
|
67
|
+
const out = await api.request('POST', `/projects/${project.projectId}/agent-sessions/revoke`);
|
|
68
|
+
if (opts.json)
|
|
69
|
+
return printJson(out);
|
|
70
|
+
info(`all project agent sessions revoked (epoch ${out.agentSessionEpoch})`);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=agent-policy.js.map
|
package/dist/commands/compute.js
CHANGED
|
@@ -429,6 +429,8 @@ function operandIndices(argv, from, to = argv.length) {
|
|
|
429
429
|
function execCommandIndex(argv) {
|
|
430
430
|
for (let cursor = 2; cursor < argv.length; cursor++) {
|
|
431
431
|
const token = argv[cursor];
|
|
432
|
+
if (token === '--agent')
|
|
433
|
+
continue;
|
|
432
434
|
if (token.startsWith('-'))
|
|
433
435
|
return -1; // a global flag, or `--`: either way not our command path
|
|
434
436
|
return token === 'compute' && argv[cursor + 1] === 'exec' ? cursor : -1;
|
package/dist/commands/govern.js
CHANGED
|
@@ -28,10 +28,10 @@ export async function approvalsList(opts) {
|
|
|
28
28
|
export async function approvalsApprove(id, opts) {
|
|
29
29
|
const api = await ApiClient.load();
|
|
30
30
|
const p = await requireProject();
|
|
31
|
-
const out = await api.request('POST', `/projects/${p.projectId}/approvals/${id}/approve`, {
|
|
31
|
+
const out = await api.request('POST', `/projects/${p.projectId}/approvals/${id}/approve`, {});
|
|
32
32
|
if (opts.json)
|
|
33
33
|
return printJson(out);
|
|
34
|
-
info(`approved ${out.approval.action} (${id})
|
|
34
|
+
info(`approved ${out.approval.action} (${id})`);
|
|
35
35
|
}
|
|
36
36
|
export async function approvalsDeny(id, opts = {}) {
|
|
37
37
|
const api = await ApiClient.load();
|
|
@@ -41,21 +41,4 @@ export async function approvalsDeny(id, opts = {}) {
|
|
|
41
41
|
return printJson(out);
|
|
42
42
|
info(`denied ${out.approval.action} (${id})`);
|
|
43
43
|
}
|
|
44
|
-
export async function policyGet(opts) {
|
|
45
|
-
const api = await ApiClient.load();
|
|
46
|
-
const p = await requireProject();
|
|
47
|
-
const { policy } = await api.request('GET', `/projects/${p.projectId}/policy`);
|
|
48
|
-
if (opts.json)
|
|
49
|
-
return printJson(policy);
|
|
50
|
-
for (const [action, decision] of Object.entries(policy))
|
|
51
|
-
info(`${action}: ${decision}`);
|
|
52
|
-
}
|
|
53
|
-
export async function policySet(action, decision, opts = {}) {
|
|
54
|
-
const api = await ApiClient.load();
|
|
55
|
-
const p = await requireProject();
|
|
56
|
-
const out = await api.request('PUT', `/projects/${p.projectId}/policy/${action}`, { decision });
|
|
57
|
-
if (opts.json)
|
|
58
|
-
return printJson({ action, decision, ...(out ?? {}) });
|
|
59
|
-
info(`policy ${action} = ${decision}`);
|
|
60
|
-
}
|
|
61
44
|
//# sourceMappingURL=govern.js.map
|
package/dist/commands/project.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
|
+
import { agentMode, setupProjectAgentSession } from '../agent.js';
|
|
2
3
|
import { ApiClient, requireProject } from '../api.js';
|
|
3
4
|
import { writeProject } from '../config.js';
|
|
4
5
|
import { info, die, printJson, handleApproval, renderNextActions } from '../util.js';
|
|
@@ -76,6 +77,8 @@ export async function projectCreate(name, opts) {
|
|
|
76
77
|
const orgId = await resolveOrg(api, opts.org);
|
|
77
78
|
const out = await api.request('POST', `/orgs/${orgId}/projects`, { name: resolved });
|
|
78
79
|
await writeProject({ projectId: out.project.id, orgId, branch: out.defaultBranch.name });
|
|
80
|
+
if (agentMode())
|
|
81
|
+
await setupProjectAgentSession(api, out.project.id);
|
|
79
82
|
if (opts.json) {
|
|
80
83
|
printJson({ ...out, linked: { projectId: out.project.id, orgId, branch: out.defaultBranch.name } });
|
|
81
84
|
}
|
|
@@ -101,6 +104,8 @@ export async function projectList(opts) {
|
|
|
101
104
|
}
|
|
102
105
|
export async function projectLink(id, opts = {}) {
|
|
103
106
|
const api = await ApiClient.load();
|
|
107
|
+
if (agentMode())
|
|
108
|
+
await setupProjectAgentSession(api, id);
|
|
104
109
|
const { project } = await api.request('GET', `/projects/${id}`);
|
|
105
110
|
await writeProject({ projectId: project.id, orgId: project.org_id, branch: 'main' });
|
|
106
111
|
if (opts.json)
|
package/dist/commands/setup.js
CHANGED
|
@@ -11,6 +11,7 @@ import { join } from 'node:path';
|
|
|
11
11
|
import os from 'node:os';
|
|
12
12
|
import { createInterface } from 'node:readline';
|
|
13
13
|
import { ApiClient } from '../api.js';
|
|
14
|
+
import { setupProjectAgentSession } from '../agent.js';
|
|
14
15
|
import { readPersistedGlobal, resolveEnv } from '../config.js';
|
|
15
16
|
import { DEFAULT_ENV, ENVS, ENV_NAMES, envForApiUrl, envFromEnvVar, isEnvName, mcpServerName } from '../env.js';
|
|
16
17
|
import { info, openUrl } from '../util.js';
|
|
@@ -362,7 +363,7 @@ export async function setupAgent(opts, run = defaultRunner, mint, installConfigs
|
|
|
362
363
|
login: () => loginDevice({}, openUrl),
|
|
363
364
|
stdinTty: canPromptViaTty(),
|
|
364
365
|
stdoutTty: !!process.stdout.isTTY,
|
|
365
|
-
}, link = projectLink, create = (n) => projectCreate(n, {})) {
|
|
366
|
+
}, link = projectLink, create = (n) => projectCreate(n, {}), enroll = async () => setupProjectAgentSession(await ApiClient.load())) {
|
|
366
367
|
if (!opts.yes && !process.stdout.isTTY) {
|
|
367
368
|
info('non-interactive shell — assuming -y');
|
|
368
369
|
}
|
|
@@ -457,6 +458,13 @@ export async function setupAgent(opts, run = defaultRunner, mint, installConfigs
|
|
|
457
458
|
}
|
|
458
459
|
}
|
|
459
460
|
}
|
|
461
|
+
if (loggedIn) {
|
|
462
|
+
if (await enroll())
|
|
463
|
+
info('✓ Project agent session ready (expires in 24 hours; refresh with insta setup agent)');
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
info(' project agent session not created — run `insta login`, then `insta setup agent`');
|
|
467
|
+
}
|
|
460
468
|
// THE summary line. The restart note exists because config-file agents only read their MCP
|
|
461
469
|
// config at startup; the skill files need no restart.
|
|
462
470
|
const mcpOk = claude === 'new' || claude === 'existing' || others.length > 0;
|
package/dist/config.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { ensureGitignore } from './gitignore.js';
|
|
5
6
|
import { DEFAULT_ENV, ENVS, envForApiUrl, envFromEnvVar, normalizeUrl } from './env.js';
|
|
6
7
|
const GLOBAL_DIR = join(homedir(), '.insta');
|
|
7
8
|
const GLOBAL_FILE = join(GLOBAL_DIR, 'config.json');
|
|
@@ -125,6 +126,7 @@ export async function readProject(cwd = process.cwd()) {
|
|
|
125
126
|
export async function writeProject(c, cwd = process.cwd()) {
|
|
126
127
|
const target = (await findProjectRoot(cwd)) ?? cwd;
|
|
127
128
|
await mkdir(join(target, PROJECT_DIR), { recursive: true });
|
|
129
|
+
ensureGitignore(target, ['.insta/agent-session.json'], '# Local agent credentials');
|
|
128
130
|
await writeFile(join(target, PROJECT_DIR, PROJECT_FILE), JSON.stringify(c, null, 2));
|
|
129
131
|
}
|
|
130
132
|
//# sourceMappingURL=config.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import {
|
|
3
|
+
import { configureAgent, detectAgent } from './agent.js';
|
|
4
|
+
import * as agentPolicy from './commands/agent-policy.js';
|
|
5
|
+
import { ApiError, AgentApprovalRequired } from './api.js';
|
|
4
6
|
import { CliCancel, CliExit, fail, relayedExitCode } from './util.js';
|
|
5
7
|
import { trackCommand } from './telemetry.js';
|
|
6
8
|
import { cliVersion } from './version.js';
|
|
@@ -32,6 +34,14 @@ import { billing, billingUpgrade, billingPortal } from './commands/billing.js';
|
|
|
32
34
|
import * as selfUpdate from './commands/upgrade.js';
|
|
33
35
|
import * as feedbackCmd from './commands/feedback.js';
|
|
34
36
|
function onError(e) {
|
|
37
|
+
if (e instanceof AgentApprovalRequired) {
|
|
38
|
+
if (process.argv.includes('--json'))
|
|
39
|
+
process.stdout.write(JSON.stringify(e.body) + '\n');
|
|
40
|
+
else
|
|
41
|
+
process.stderr.write(e.message + '\n');
|
|
42
|
+
process.exitCode = 2;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
35
45
|
if (e instanceof CliExit || e instanceof CliCancel)
|
|
36
46
|
return;
|
|
37
47
|
if (e instanceof ApiError)
|
|
@@ -64,6 +74,8 @@ const program = new Command();
|
|
|
64
74
|
// against the subcommand's own (identically-named) option instead.
|
|
65
75
|
program.enablePositionalOptions();
|
|
66
76
|
program.name('insta').description('InstaCloud CLI — manage projects, branches, secrets, deploys').version(cliVersion());
|
|
77
|
+
program.option('--agent', 'run as an agent with a verified project session and project agent policy');
|
|
78
|
+
program.hook('preAction', () => configureAgent(detectAgent(!!program.opts().agent)));
|
|
67
79
|
// ---- auth ----
|
|
68
80
|
program.command('login').description('Log in — bare: sign in from your browser (any account type); or --email <email> + password, --oauth <github|google>, --device (headless), --api-key <insta_…> (headless, durable token)')
|
|
69
81
|
.option('--email <email>', 'account email (email + password login)')
|
|
@@ -324,7 +336,7 @@ program.command('events').description('Show the audit + agent-event timeline').o
|
|
|
324
336
|
// ---- approvals ----
|
|
325
337
|
const ap = program.command('approvals').description('Governance approvals (HITL)');
|
|
326
338
|
ap.command('list').option('--status <s>', 'pending|granted|denied|consumed').option('--json').action(guard((o) => govern.approvalsList(o)));
|
|
327
|
-
ap.command('approve <id>').option('--
|
|
339
|
+
ap.command('approve <id>').option('--json').action(guard((id, o) => govern.approvalsApprove(id, o)));
|
|
328
340
|
ap.command('deny <id>').option('--json').action(guard((id, o) => govern.approvalsDeny(id, o)));
|
|
329
341
|
// ---- observe (local credential audit) ----
|
|
330
342
|
const ob = program.command('observe').description('Local credential-audit hook');
|
|
@@ -333,9 +345,16 @@ ob.command('uninstall').action(guard(() => observe.observeUninstall()));
|
|
|
333
345
|
ob.command('report').description('Render the local credential audit').option('--json').action(guard((o) => observe.observeReport(o)));
|
|
334
346
|
ob.command('sync').description('Upload findings into the project timeline').action(guard(() => observe.observeSync()));
|
|
335
347
|
// ---- policy ----
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
348
|
+
const agentPol = program.command('agent-policy').description('Project agent access policy');
|
|
349
|
+
agentPol.command('get').option('--json').action(guard((o) => agentPolicy.get(o)));
|
|
350
|
+
agentPol.command('set <mode>').description('full-access | read-only | branch-developer')
|
|
351
|
+
.option('--json').action(guard((mode, o) => agentPolicy.set(mode, o)));
|
|
352
|
+
agentPol.command('protect-branch <branch>').option('--json').action(guard((branch, o) => agentPolicy.protect(branch, true, o)));
|
|
353
|
+
agentPol.command('unprotect-branch <branch>').option('--json').action(guard((branch, o) => agentPolicy.protect(branch, false, o)));
|
|
354
|
+
agentPol.command('rule').command('set <action> <decision>').description('Set an unprotected-branch rule: allow | deny | approve')
|
|
355
|
+
.option('--json').action(guard((action, decision, o) => agentPolicy.rule(action, decision, o)));
|
|
356
|
+
agentPol.command('revoke-sessions').description('Revoke ALL CLI agent sessions for this project')
|
|
357
|
+
.option('--json').action(guard((o) => agentPolicy.revoke(o)));
|
|
339
358
|
// ---- feedback (agent + human hurdle reports → the InstaCloud team) ----
|
|
340
359
|
program.command('feedback')
|
|
341
360
|
.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.')
|
package/dist/telemetry.js
CHANGED
|
@@ -39,7 +39,7 @@ const SAFE_ARGS = {
|
|
|
39
39
|
'compute always-on': { 0: ON_OFF }, 'db always-on': { 0: ON_OFF }, metrics: { 0: TARGET }, logs: { 0: TARGET },
|
|
40
40
|
'template info': { 0: SLUG }, 'billing upgrade': { 0: oneOf(['pro', 'team']) },
|
|
41
41
|
'approvals approve': { 0: ID }, 'approvals deny': { 0: ID },
|
|
42
|
-
'policy set': { 0: POLICY_ACTION, 1: oneOf(['allow', 'deny', 'approve']) }, autoupdate: { 0: ON_OFF },
|
|
42
|
+
'agent-policy rule set': { 0: POLICY_ACTION, 1: oneOf(['allow', 'deny', 'approve']) }, autoupdate: { 0: ON_OFF },
|
|
43
43
|
};
|
|
44
44
|
const SAFE_OPTIONS = {
|
|
45
45
|
org: ID, project: ID, region: REGION, env: ENV, oauth: oneOf(['github', 'google']),
|