@use-aistack/cli 0.7.0 → 0.7.2
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 +7 -7
- package/dist/index.js +67 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Measure and share your AI stack. The CLI scans your local Claude Code transcripts, shows you exactly what would go up, and publishes only after you approve in your terminal.
|
|
4
4
|
|
|
5
|
-
Run on-demand with `npx`
|
|
5
|
+
Run on-demand with `npx` - no install required:
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
npx @use-aistack/cli sync
|
|
@@ -12,17 +12,17 @@ npx @use-aistack/cli sync
|
|
|
12
12
|
|
|
13
13
|
### `npx @use-aistack/cli sync`
|
|
14
14
|
|
|
15
|
-
The default command. Scan your local transcripts (rolling 30 days), review the full summary, and publish the measured layer to your stack. Nothing leaves your machine without your explicit approval at the prompt. Raw transcripts, prompts, and paths never leave at all
|
|
15
|
+
The default command. Scan your local transcripts (rolling 30 days), review the full summary, and publish the measured layer to your stack. Nothing leaves your machine without your explicit approval at the prompt. Raw transcripts, prompts, and paths never leave at all - only aggregates.
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
18
|
npx @use-aistack/cli sync
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
On an unlinked machine, `sync` starts the login flow inline
|
|
21
|
+
On an unlinked machine, `sync` starts the login flow inline - your browser opens to approve the machine, then the sync continues. One command is the whole onboarding.
|
|
22
22
|
|
|
23
23
|
### `npx @use-aistack/cli sync --auto on` / `off`
|
|
24
24
|
|
|
25
|
-
Optional: keep your stack fresh without manual syncs. `on` asks your stack for the permission, then writes a `SessionStart` hook into the harnesses you actually use
|
|
25
|
+
Optional: keep your stack fresh without manual syncs. `on` asks your stack for the permission, then writes a `SessionStart` hook into the harnesses you actually use - `~/.claude/settings.json` for Claude Code, `~/.codex/hooks.json` for Codex. The hook runs a silent sync at most once per day when a session starts. `off` removes the hooks and takes the permission back.
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
28
|
npx @use-aistack/cli sync --auto on # enable, default every 24h
|
|
@@ -30,13 +30,13 @@ npx @use-aistack/cli sync --auto on --every 12 # custom frequency in hours
|
|
|
30
30
|
npx @use-aistack/cli sync --auto off # revoke
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
**Your stack owns the permission, not this machine.** The silent run asks aistack.to before it publishes anything, so the switch on your stack page is a complete revoke: it stops every machine, even one whose hooks are still installed. It works the other way too
|
|
33
|
+
**Your stack owns the permission, not this machine.** The silent run asks aistack.to before it publishes anything, so the switch on your stack page is a complete revoke: it stops every machine, even one whose hooks are still installed. It works the other way too - turn the switch on, run one `sync`, and that machine installs its own triggers. A harness you adopt months later gets its trigger the same way.
|
|
34
34
|
|
|
35
35
|
The silent run (`sync --auto`) never prompts and never installs a hook. Each run appends one line to `~/.config/aistack/sync.log` (capped at 200 lines). The next interactive `sync` reports the last result. After 3 failures in a row, one visible message appears in Claude Code and names the fix. No email, no dialogs.
|
|
36
36
|
|
|
37
37
|
### `npx @use-aistack/cli login`
|
|
38
38
|
|
|
39
|
-
Link this machine to your AI Stack account via browser. Optional
|
|
39
|
+
Link this machine to your AI Stack account via browser. Optional - `sync` runs this inline when the machine is not linked yet.
|
|
40
40
|
|
|
41
41
|
```sh
|
|
42
42
|
npx @use-aistack/cli login
|
|
@@ -44,7 +44,7 @@ npx @use-aistack/cli login
|
|
|
44
44
|
|
|
45
45
|
### `npx @use-aistack/cli connect claude`
|
|
46
46
|
|
|
47
|
-
Optional: install the in-session sync surface for Claude Code
|
|
47
|
+
Optional: install the in-session sync surface for Claude Code - the `aistack` MCP server (user scope) plus the `aistack-sync` Skill. After that, say "sync my stack" in any Claude Code session. Both halves install together or not at all.
|
|
48
48
|
|
|
49
49
|
```sh
|
|
50
50
|
npx @use-aistack/cli connect claude
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
|
-
var CLI_VERSION = true ? "0.7.
|
|
7
|
+
var CLI_VERSION = true ? "0.7.2" : "0.0.0-dev";
|
|
8
8
|
|
|
9
9
|
// src/api.ts
|
|
10
10
|
var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
|
|
@@ -76,11 +76,11 @@ async function formatHttpError(res, label) {
|
|
|
76
76
|
try {
|
|
77
77
|
const body = JSON.parse(text);
|
|
78
78
|
const detail = body.error || body.message;
|
|
79
|
-
if (detail) return `${prefix}
|
|
79
|
+
if (detail) return `${prefix} - ${detail}`;
|
|
80
80
|
} catch {
|
|
81
81
|
}
|
|
82
82
|
const snippet = text.trim().slice(0, 500);
|
|
83
|
-
return snippet ? `${prefix}
|
|
83
|
+
return snippet ? `${prefix} - ${snippet}` : prefix;
|
|
84
84
|
}
|
|
85
85
|
async function syncPublish(token, bodyJson) {
|
|
86
86
|
const res = await request("/api/cli/sync", {
|
|
@@ -768,7 +768,7 @@ var LOCAL_PATTERNS = [
|
|
|
768
768
|
{ path: ".clinerules", type: "rule", group: "cline" },
|
|
769
769
|
{ path: ".roorules", type: "rule", group: "roo" },
|
|
770
770
|
{ path: ".github/copilot-instructions.md", type: "rule", group: "copilot" },
|
|
771
|
-
// MCP servers are detected separately as pkg-reference links (see mcp.ts)
|
|
771
|
+
// MCP servers are detected separately as pkg-reference links (see mcp.ts) -
|
|
772
772
|
// their config files are intentionally NOT collected as content here (which
|
|
773
773
|
// would also upload `env` secrets).
|
|
774
774
|
// Config
|
|
@@ -1425,11 +1425,11 @@ var PRICES = {
|
|
|
1425
1425
|
]),
|
|
1426
1426
|
"claude-sonnet-4-6": anthropic(flat(3, 15)),
|
|
1427
1427
|
"claude-haiku-4-5": anthropic(flat(1, 5)),
|
|
1428
|
-
// Fast mode (research preview)
|
|
1428
|
+
// Fast mode (research preview) - Claude API only, Opus 5 / Opus 4.8 only.
|
|
1429
1429
|
// Opus 4.7 fast mode was removed, so there is deliberately no 4-7 entry.
|
|
1430
1430
|
"claude-opus-5#fast": anthropic(flat(10, 50)),
|
|
1431
1431
|
"claude-opus-4-8#fast": anthropic(flat(10, 50)),
|
|
1432
|
-
// OpenAI (Codex)
|
|
1432
|
+
// OpenAI (Codex) - standard-context tier (<272K; observed context window is
|
|
1433
1433
|
// 258,400).
|
|
1434
1434
|
"gpt-5.5": openai(flat(5, 30)),
|
|
1435
1435
|
"gpt-5.4": openai(flat(2.5, 15)),
|
|
@@ -1437,21 +1437,21 @@ var PRICES = {
|
|
|
1437
1437
|
"gpt-5.3-codex": openai(flat(1.75, 14)),
|
|
1438
1438
|
// The gpt-5.6 family launched 2026-07-29; Terra and Luna were repriced on
|
|
1439
1439
|
// 2026-07-30 (-20% / -80%). The one-day launch rates are not on the list
|
|
1440
|
-
// page and are NOT encoded
|
|
1440
|
+
// page and are NOT encoded - a July-29 Terra/Luna record underprices for
|
|
1441
1441
|
// one day rather than carrying a rate we cannot cite (#72).
|
|
1442
1442
|
"gpt-5.6-sol": openai(flat(5, 30)),
|
|
1443
1443
|
"gpt-5.6-terra": openai(flat(2, 12)),
|
|
1444
1444
|
"gpt-5.6-luna": openai(flat(0.2, 1.2)),
|
|
1445
|
-
// NOT on OpenAI's list page
|
|
1445
|
+
// NOT on OpenAI's list page - an internal Codex routing label with no
|
|
1446
1446
|
// official price (openai/codex#20981). Rate is the aggregator consensus
|
|
1447
1447
|
// ($2.50 / $15.00), scoped in explicitly by ticket #72 because it carries
|
|
1448
1448
|
// real token volume in Codex rollouts.
|
|
1449
1449
|
"codex-auto-review": openai(flat(2.5, 15)),
|
|
1450
|
-
// Google (opencode, pi-mono)
|
|
1450
|
+
// Google (opencode, pi-mono) - Standard tier. Where a model is
|
|
1451
1451
|
// context-tiered, the <=200K rate is encoded, exactly as the OpenAI rows
|
|
1452
1452
|
// encode the standard-context tier: the payload carries no per-response
|
|
1453
1453
|
// context length, so the cheaper side keeps the figure a lower bound. Only
|
|
1454
|
-
// the Pro and Flash families are here
|
|
1454
|
+
// the Pro and Flash families are here - image, TTS, embedding, Live and
|
|
1455
1455
|
// robotics models are not what a coding harness selects.
|
|
1456
1456
|
"gemini-3.1-pro-preview": google(flat(2, 12)),
|
|
1457
1457
|
"gemini-3.6-flash": google(flat(1.5, 7.5)),
|
|
@@ -1903,7 +1903,7 @@ var BUNDLED_SYNC_CONFIG = {
|
|
|
1903
1903
|
// upload the names it is holding back. The default is ON server-side, so this
|
|
1904
1904
|
// costs the owner one retry and never costs them a name.
|
|
1905
1905
|
reviewKeptPrivate: false,
|
|
1906
|
-
// No fetch, no destination
|
|
1906
|
+
// No fetch, no destination - and the gate refuses to publish without one.
|
|
1907
1907
|
stack: null,
|
|
1908
1908
|
// No fetch, no permission either. This costs nothing on its own: `stack` is
|
|
1909
1909
|
// null in the same breath, so the stage blocks before any publish.
|
|
@@ -1974,7 +1974,7 @@ function readSyncConfig(raw) {
|
|
|
1974
1974
|
},
|
|
1975
1975
|
// Anything other than an explicit `true` fails closed.
|
|
1976
1976
|
publishCost: obj.publishCost === true,
|
|
1977
|
-
// Absent means "no stack resolved"
|
|
1977
|
+
// Absent means "no stack resolved" - an anonymous fetch, or a token bound
|
|
1978
1978
|
// to nothing. Both fail closed to publishing no user-chosen names.
|
|
1979
1979
|
optIns: readOptIns(obj.optIns),
|
|
1980
1980
|
// Anything other than an explicit `true` keeps the names on the machine.
|
|
@@ -3369,9 +3369,9 @@ function readCounts2(usageRaw) {
|
|
|
3369
3369
|
const split = typeof u.cacheWrite1h === "number" && Number.isFinite(u.cacheWrite1h);
|
|
3370
3370
|
const cacheWrite1h = split ? Math.min(Math.max(u.cacheWrite1h, 0), cacheWrite) : 0;
|
|
3371
3371
|
return {
|
|
3372
|
-
// Already exclusive of cache traffic
|
|
3372
|
+
// Already exclusive of cache traffic - no subtraction (research §2).
|
|
3373
3373
|
input: asNum(u.input),
|
|
3374
|
-
// `reasoning` is a subset of `output`
|
|
3374
|
+
// `reasoning` is a subset of `output` - never added.
|
|
3375
3375
|
output: asNum(u.output),
|
|
3376
3376
|
cacheWrite5m: split ? cacheWrite - cacheWrite1h : 0,
|
|
3377
3377
|
cacheWrite1h,
|
|
@@ -3784,7 +3784,7 @@ function harnessLabel(name) {
|
|
|
3784
3784
|
if (name === CLAUDE_HARNESS_NAME) return "Claude Code";
|
|
3785
3785
|
if (name === CODEX_HARNESS_NAME) return "Codex";
|
|
3786
3786
|
if (name === OPENCODE_HARNESS_NAME) return "opencode";
|
|
3787
|
-
if (name === PI_HARNESS_NAME) return "
|
|
3787
|
+
if (name === PI_HARNESS_NAME) return "Pi";
|
|
3788
3788
|
return name;
|
|
3789
3789
|
}
|
|
3790
3790
|
function harnessListLabel(adapters) {
|
|
@@ -3845,14 +3845,14 @@ function installClaudeConnect(run = runClaude, copySkill = (src, dest) => cpSync
|
|
|
3845
3845
|
if (source === null) {
|
|
3846
3846
|
return {
|
|
3847
3847
|
ok: false,
|
|
3848
|
-
message: "this install is missing its bundled Skill (skills/aistack-sync)
|
|
3848
|
+
message: "this install is missing its bundled Skill (skills/aistack-sync) - nothing was installed"
|
|
3849
3849
|
};
|
|
3850
3850
|
}
|
|
3851
3851
|
const add = run(MCP_ADD_ARGS);
|
|
3852
3852
|
if (add.notFound) {
|
|
3853
3853
|
return {
|
|
3854
3854
|
ok: false,
|
|
3855
|
-
message: `claude was not found on PATH
|
|
3855
|
+
message: `claude was not found on PATH - nothing was installed. Manual install:
|
|
3856
3856
|
${MANUAL_MCP_ADD}`
|
|
3857
3857
|
};
|
|
3858
3858
|
}
|
|
@@ -3860,7 +3860,7 @@ ${MANUAL_MCP_ADD}`
|
|
|
3860
3860
|
if (add.status !== 0 && !alreadyRegistered) {
|
|
3861
3861
|
return {
|
|
3862
3862
|
ok: false,
|
|
3863
|
-
message: `claude mcp add failed
|
|
3863
|
+
message: `claude mcp add failed - nothing was installed.
|
|
3864
3864
|
${add.output.trim()}`
|
|
3865
3865
|
};
|
|
3866
3866
|
}
|
|
@@ -3870,7 +3870,7 @@ ${add.output.trim()}`
|
|
|
3870
3870
|
if (!alreadyRegistered) run(MCP_REMOVE_ARGS);
|
|
3871
3871
|
return {
|
|
3872
3872
|
ok: false,
|
|
3873
|
-
message: `copying the Skill to ${SKILL_DEST} failed
|
|
3873
|
+
message: `copying the Skill to ${SKILL_DEST} failed - the MCP registration was ${alreadyRegistered ? "left as it was" : "rolled back"}.
|
|
3874
3874
|
${e instanceof Error ? e.message : String(e)}`
|
|
3875
3875
|
};
|
|
3876
3876
|
}
|
|
@@ -3882,7 +3882,7 @@ ${e instanceof Error ? e.message : String(e)}`
|
|
|
3882
3882
|
async function connectCommand(harness) {
|
|
3883
3883
|
intro2("connect");
|
|
3884
3884
|
if (harness !== "claude") {
|
|
3885
|
-
outroError(`unknown harness "${harness}"
|
|
3885
|
+
outroError(`unknown harness "${harness}" - supported: claude`);
|
|
3886
3886
|
process.exitCode = 1;
|
|
3887
3887
|
return;
|
|
3888
3888
|
}
|
|
@@ -4147,10 +4147,10 @@ function readHooksJson(file) {
|
|
|
4147
4147
|
}
|
|
4148
4148
|
return { error: `${file} does not hold a JSON object` };
|
|
4149
4149
|
} catch {
|
|
4150
|
-
return { error: `${file} is not valid JSON
|
|
4150
|
+
return { error: `${file} is not valid JSON - fix it, then retry` };
|
|
4151
4151
|
}
|
|
4152
4152
|
}
|
|
4153
|
-
var CODEX_TRUST_INSTRUCTION = "Codex hook written
|
|
4153
|
+
var CODEX_TRUST_INSTRUCTION = "Codex hook written - open Codex and run /hooks once to trust it, or it will not run.";
|
|
4154
4154
|
function installCodexAutoSyncHook(file = codexHooksFile()) {
|
|
4155
4155
|
const read = readHooksJson(file);
|
|
4156
4156
|
if ("error" in read) return { ok: false, message: read.error };
|
|
@@ -4239,7 +4239,7 @@ function readClaudeSettings(file) {
|
|
|
4239
4239
|
}
|
|
4240
4240
|
return { error: `${file} does not hold a JSON object` };
|
|
4241
4241
|
} catch {
|
|
4242
|
-
return { error: `${file} is not valid JSON
|
|
4242
|
+
return { error: `${file} is not valid JSON - fix it, then retry` };
|
|
4243
4243
|
}
|
|
4244
4244
|
}
|
|
4245
4245
|
function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
@@ -4339,7 +4339,7 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
|
|
|
4339
4339
|
return {
|
|
4340
4340
|
ok: true,
|
|
4341
4341
|
message: [
|
|
4342
|
-
`Auto-sync is on
|
|
4342
|
+
`Auto-sync is on. It runs about every ${frequencyHours}h when a ${harnessListLabel(detected)} session starts. Turn it off any time: npx @use-aistack/cli sync --auto off`,
|
|
4343
4343
|
...trustLine ? [trustLine] : []
|
|
4344
4344
|
].join("\n")
|
|
4345
4345
|
};
|
|
@@ -4365,7 +4365,7 @@ async function disableAutoSync(deps = {}) {
|
|
|
4365
4365
|
await (deps.setAutoSyncImpl ?? setAutoSync)(token, { enabled: false });
|
|
4366
4366
|
} catch (e) {
|
|
4367
4367
|
failures.push(
|
|
4368
|
-
`aistack.to was not told (${e instanceof Error ? e.message : String(e)})
|
|
4368
|
+
`aistack.to was not told (${e instanceof Error ? e.message : String(e)}); your other machines keep the permission`
|
|
4369
4369
|
);
|
|
4370
4370
|
}
|
|
4371
4371
|
}
|
|
@@ -4421,7 +4421,7 @@ async function reconcileAutoSync(permission, deps = {}) {
|
|
|
4421
4421
|
if (installed.length === 0 && mirrored) return null;
|
|
4422
4422
|
return {
|
|
4423
4423
|
ok: true,
|
|
4424
|
-
message: installed.length > 0 ? `Auto-sync is on for this stack
|
|
4424
|
+
message: installed.length > 0 ? `Auto-sync is on for this stack. Installed the ${installed.join(" and ")} trigger on this machine; it runs about every ${frequencyHours}h when a session starts.` : `Auto-sync is on for this stack. It runs about every ${frequencyHours}h when a ${harnessListLabel(detected)} session starts.`
|
|
4425
4425
|
};
|
|
4426
4426
|
}
|
|
4427
4427
|
async function settleAutoSync(permission, deps = {}) {
|
|
@@ -4504,6 +4504,9 @@ function fmtUSD(n) {
|
|
|
4504
4504
|
return `\u2248$${Math.round(n).toLocaleString("en-US")}`;
|
|
4505
4505
|
}
|
|
4506
4506
|
var fmtPct = (share) => `${(share * 100).toFixed(1)}%`;
|
|
4507
|
+
function fmtReceivedAt(ms) {
|
|
4508
|
+
return `${new Date(ms).toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
4509
|
+
}
|
|
4507
4510
|
function totalUSD(payload) {
|
|
4508
4511
|
let sum = 0;
|
|
4509
4512
|
let any = false;
|
|
@@ -4581,18 +4584,16 @@ function scanNoteLines(stats, label) {
|
|
|
4581
4584
|
if (stats.filesForeign > 0) {
|
|
4582
4585
|
const origins = [...stats.foreignOriginators].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([name, n]) => n > 1 ? `${name} \xD7${n}` : name).join(", ");
|
|
4583
4586
|
out.push(
|
|
4584
|
-
`skipped ${stats.filesForeign} file${stats.filesForeign === 1 ? "" : "s"} not written by ${label}
|
|
4587
|
+
`skipped ${stats.filesForeign} file${stats.filesForeign === 1 ? "" : "s"} not written by ${label} (originators: ${origins})`
|
|
4585
4588
|
);
|
|
4586
4589
|
}
|
|
4587
4590
|
return out;
|
|
4588
4591
|
}
|
|
4589
|
-
function payloadBlock(payload,
|
|
4592
|
+
function payloadBlock(payload, stats) {
|
|
4590
4593
|
const out = [];
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
);
|
|
4595
|
-
}
|
|
4594
|
+
out.push(
|
|
4595
|
+
`- ${harnessLabel(payload.harness.name)}${payload.harness.version ? ` ${payload.harness.version}` : ""}`
|
|
4596
|
+
);
|
|
4596
4597
|
out.push(
|
|
4597
4598
|
`window ${payload.window.days} days \xB7 ${payload.window.from} \u2192 ${payload.window.to}`
|
|
4598
4599
|
);
|
|
@@ -4606,7 +4607,7 @@ function payloadBlock(payload, showHeader, stats) {
|
|
|
4606
4607
|
const cov = payload.coverage;
|
|
4607
4608
|
if (cov.filesUnreadable > 0 || cov.linesFailed > 0) {
|
|
4608
4609
|
out.push(
|
|
4609
|
-
`coverage ${cov.filesUnreadable} files unreadable \xB7 ${cov.linesFailed} lines failed \
|
|
4610
|
+
`coverage ${cov.filesUnreadable} files unreadable \xB7 ${cov.linesFailed} lines failed \xB7 this reading is a floor`
|
|
4610
4611
|
);
|
|
4611
4612
|
}
|
|
4612
4613
|
if (stats) {
|
|
@@ -4633,19 +4634,22 @@ function buildGateSummary(ctx) {
|
|
|
4633
4634
|
const { payloads } = body;
|
|
4634
4635
|
const host = baseUrl.replace(/^https?:\/\//, "");
|
|
4635
4636
|
const out = [];
|
|
4636
|
-
out.push("from your machine \
|
|
4637
|
+
out.push("from your machine \xB7 sync preview");
|
|
4637
4638
|
out.push("");
|
|
4638
4639
|
if (config.stack === null) {
|
|
4639
|
-
out.push("to (no linked stack
|
|
4640
|
+
out.push("to (no linked stack; publish is unavailable)");
|
|
4640
4641
|
} else {
|
|
4641
4642
|
out.push(
|
|
4642
4643
|
`to ${config.stack.name} \xB7 ${host}/stacks/${config.stack.slug}`
|
|
4643
4644
|
);
|
|
4644
4645
|
}
|
|
4646
|
+
out.push(
|
|
4647
|
+
`searched ${HARNESS_ADAPTERS.map((a) => harnessLabel(a.name).toLowerCase()).join(", ")}`
|
|
4648
|
+
);
|
|
4645
4649
|
for (const payload of payloads) {
|
|
4646
4650
|
const stats = ctx.scanStats?.[payload.harness.name];
|
|
4647
|
-
out.push(...payloadBlock(payload, payloads.length > 1, stats));
|
|
4648
4651
|
out.push("");
|
|
4652
|
+
out.push(...payloadBlock(payload, stats));
|
|
4649
4653
|
}
|
|
4650
4654
|
if (out[out.length - 1] === "") out.pop();
|
|
4651
4655
|
const n = payloads.reduce((a, p8) => a + withheldCount(p8), 0);
|
|
@@ -4664,7 +4668,7 @@ function buildGateSummary(ctx) {
|
|
|
4664
4668
|
if (body.keptPrivate !== void 0 && config.stack !== null) {
|
|
4665
4669
|
out.push(` publish them at ${host}/stacks/${config.stack.slug}/changes`);
|
|
4666
4670
|
out.push(
|
|
4667
|
-
" (they go up for you to review
|
|
4671
|
+
" (they go up for you to review - turn off: Review kept-private names, on your stack)"
|
|
4668
4672
|
);
|
|
4669
4673
|
} else {
|
|
4670
4674
|
out.push(" they stay on this machine");
|
|
@@ -4679,7 +4683,7 @@ function buildGateSummary(ctx) {
|
|
|
4679
4683
|
if (source === "bundled") {
|
|
4680
4684
|
out.push("");
|
|
4681
4685
|
out.push(
|
|
4682
|
-
"! could not fetch your settings from aistack
|
|
4686
|
+
"! could not fetch your settings from aistack - using the bundled list."
|
|
4683
4687
|
);
|
|
4684
4688
|
out.push(
|
|
4685
4689
|
" This publishes less: no cost, no ticked names, nothing staged for review."
|
|
@@ -4734,7 +4738,7 @@ async function stageSync(deps) {
|
|
|
4734
4738
|
};
|
|
4735
4739
|
let blockedReason = null;
|
|
4736
4740
|
if (built.length === 0) {
|
|
4737
|
-
blockedReason = `No active harness on this machine
|
|
4741
|
+
blockedReason = `No active harness on this machine - no Claude Code and no Codex transcript from the last ${windowDays} days to read.`;
|
|
4738
4742
|
} else if (token === null) {
|
|
4739
4743
|
blockedReason = "This machine is not linked. Run `npx @use-aistack/cli login` first.";
|
|
4740
4744
|
} else if (config.stack === null) {
|
|
@@ -4758,7 +4762,7 @@ async function stageSync(deps) {
|
|
|
4758
4762
|
var SYNC_LOG_FILE = join10(homedir13(), ".config", "aistack", "sync.log");
|
|
4759
4763
|
var SYNC_LOG_MAX_LINES = 200;
|
|
4760
4764
|
var FIX_COMMAND = "npx @use-aistack/cli sync";
|
|
4761
|
-
var REVOKED_RESULT = "off
|
|
4765
|
+
var REVOKED_RESULT = "off - auto-sync is switched off for this stack";
|
|
4762
4766
|
function appendLogLine(file, line) {
|
|
4763
4767
|
mkdirSync5(dirname7(file), { recursive: true });
|
|
4764
4768
|
appendFileSync(file, `${line}
|
|
@@ -4779,7 +4783,7 @@ async function runAutoSync(deps) {
|
|
|
4779
4783
|
const settings = getSettings(settingsFile);
|
|
4780
4784
|
const config = settings.autoSync;
|
|
4781
4785
|
if (config?.enabled !== true) {
|
|
4782
|
-
appendLogLine(logFile, `${stamp} skipped
|
|
4786
|
+
appendLogLine(logFile, `${stamp} skipped - auto-sync is not enabled`);
|
|
4783
4787
|
return;
|
|
4784
4788
|
}
|
|
4785
4789
|
const frequencyHours = config.frequencyHours || DEFAULT_FREQUENCY_HOURS;
|
|
@@ -4827,7 +4831,7 @@ async function runAutoSync(deps) {
|
|
|
4827
4831
|
},
|
|
4828
4832
|
settingsFile
|
|
4829
4833
|
);
|
|
4830
|
-
appendLogLine(logFile, `${stamp} skipped
|
|
4834
|
+
appendLogLine(logFile, `${stamp} skipped - ${REVOKED_RESULT}`);
|
|
4831
4835
|
return;
|
|
4832
4836
|
}
|
|
4833
4837
|
if (failure2 === null) {
|
|
@@ -4836,14 +4840,14 @@ async function runAutoSync(deps) {
|
|
|
4836
4840
|
autoSyncState: {
|
|
4837
4841
|
lastRunAt: now,
|
|
4838
4842
|
lastSuccessAt: now,
|
|
4839
|
-
lastResult: `ok
|
|
4843
|
+
lastResult: `ok - published at ${stamp}`,
|
|
4840
4844
|
consecutiveFailures: 0,
|
|
4841
4845
|
failureWarned: false
|
|
4842
4846
|
}
|
|
4843
4847
|
},
|
|
4844
4848
|
settingsFile
|
|
4845
4849
|
);
|
|
4846
|
-
appendLogLine(logFile, `${stamp} ok
|
|
4850
|
+
appendLogLine(logFile, `${stamp} ok - published${url ? ` ${url}` : ""}`);
|
|
4847
4851
|
return;
|
|
4848
4852
|
}
|
|
4849
4853
|
const consecutiveFailures = (state.consecutiveFailures ?? 0) + 1;
|
|
@@ -4853,7 +4857,7 @@ async function runAutoSync(deps) {
|
|
|
4853
4857
|
autoSyncState: {
|
|
4854
4858
|
...state,
|
|
4855
4859
|
lastRunAt: now,
|
|
4856
|
-
lastResult: `failed at ${stamp}
|
|
4860
|
+
lastResult: `failed at ${stamp} - ${failure2}`,
|
|
4857
4861
|
consecutiveFailures,
|
|
4858
4862
|
failureWarned: state.failureWarned === true || shouldWarn
|
|
4859
4863
|
}
|
|
@@ -4862,7 +4866,7 @@ async function runAutoSync(deps) {
|
|
|
4862
4866
|
);
|
|
4863
4867
|
appendLogLine(
|
|
4864
4868
|
logFile,
|
|
4865
|
-
`${stamp} fail (${consecutiveFailures} in a row)
|
|
4869
|
+
`${stamp} fail (${consecutiveFailures} in a row) - ${failure2}`
|
|
4866
4870
|
);
|
|
4867
4871
|
if (shouldWarn) {
|
|
4868
4872
|
emit(
|
|
@@ -4895,7 +4899,7 @@ async function syncCommand(options = {}) {
|
|
|
4895
4899
|
}
|
|
4896
4900
|
if (options.auto !== void 0) {
|
|
4897
4901
|
intro2("sync");
|
|
4898
|
-
outroError(`unknown --auto value "${options.auto}"
|
|
4902
|
+
outroError(`unknown --auto value "${options.auto}" (use on or off)`);
|
|
4899
4903
|
process.exitCode = 1;
|
|
4900
4904
|
return;
|
|
4901
4905
|
}
|
|
@@ -4908,16 +4912,16 @@ async function syncCommand(options = {}) {
|
|
|
4908
4912
|
p7.log.warn(CODEX_TRUST_INSTRUCTION);
|
|
4909
4913
|
}
|
|
4910
4914
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
4911
|
-
outroError("sync needs an interactive terminal
|
|
4915
|
+
outroError("sync needs an interactive terminal. Nothing was sent.");
|
|
4912
4916
|
process.exitCode = 1;
|
|
4913
4917
|
return;
|
|
4914
4918
|
}
|
|
4915
4919
|
if (getToken() === null) {
|
|
4916
4920
|
p7.log.message(
|
|
4917
|
-
"This machine is not linked to an aistack account yet
|
|
4921
|
+
"This machine is not linked to an aistack account yet. Linking it now."
|
|
4918
4922
|
);
|
|
4919
4923
|
if (!await performLogin()) {
|
|
4920
|
-
outroError("login failed
|
|
4924
|
+
outroError("login failed. Nothing was sent.");
|
|
4921
4925
|
process.exitCode = 1;
|
|
4922
4926
|
return;
|
|
4923
4927
|
}
|
|
@@ -4957,12 +4961,14 @@ async function syncCommand(options = {}) {
|
|
|
4957
4961
|
const res = await syncPublish(staged.token, staged.bodyJson);
|
|
4958
4962
|
s.stop("Published");
|
|
4959
4963
|
const lines2 = [
|
|
4960
|
-
`Snapshot received
|
|
4964
|
+
`Snapshot received ${fmtReceivedAt(res.receivedAt)}`,
|
|
4965
|
+
"",
|
|
4966
|
+
"Your stack now shows what actually ran:",
|
|
4961
4967
|
lime(res.url)
|
|
4962
4968
|
];
|
|
4963
4969
|
if (res.keptPrivate.refused && staged.body.keptPrivate !== void 0) {
|
|
4964
4970
|
lines2.push(
|
|
4965
|
-
"Note: the kept-private names
|
|
4971
|
+
"Note: the server refused the kept-private names because its review switch is off. They stayed on this machine."
|
|
4966
4972
|
);
|
|
4967
4973
|
} else if (res.keptPrivate.stored > 0) {
|
|
4968
4974
|
lines2.push(
|
|
@@ -4987,10 +4993,10 @@ var STAGE_TTL_MS = 10 * 60 * 1e3;
|
|
|
4987
4993
|
var ELICIT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
4988
4994
|
var PREVIEW_TOOL = {
|
|
4989
4995
|
name: "sync_preview",
|
|
4990
|
-
description: "Scan local agent transcripts (Claude Code, Codex) and stage a measured-usage snapshot for aistack. Returns the full preview of exactly what would publish. Show the returned text to the user VERBATIM
|
|
4996
|
+
description: "Scan local agent transcripts (Claude Code, Codex) and stage a measured-usage snapshot for aistack. Returns the full preview of exactly what would publish. Show the returned text to the user VERBATIM - it is the review surface. Nothing is sent.",
|
|
4991
4997
|
inputSchema: { type: "object", properties: {} },
|
|
4992
4998
|
annotations: {
|
|
4993
|
-
title: "aistack
|
|
4999
|
+
title: "aistack - preview sync (sends nothing)",
|
|
4994
5000
|
readOnlyHint: true,
|
|
4995
5001
|
openWorldHint: true
|
|
4996
5002
|
}
|
|
@@ -5009,7 +5015,7 @@ var PUBLISH_TOOL = {
|
|
|
5009
5015
|
required: ["preview_id"]
|
|
5010
5016
|
},
|
|
5011
5017
|
annotations: {
|
|
5012
|
-
title: "aistack
|
|
5018
|
+
title: "aistack - publish measured usage (asks the user first)",
|
|
5013
5019
|
destructiveHint: false,
|
|
5014
5020
|
openWorldHint: true
|
|
5015
5021
|
}
|
|
@@ -5064,7 +5070,7 @@ function createSyncServer(deps, send) {
|
|
|
5064
5070
|
return ok(
|
|
5065
5071
|
id,
|
|
5066
5072
|
textResult(
|
|
5067
|
-
"Not published: this Claude Code version did not declare the elicitation capability, so the approve dialog cannot be shown. The gate never degrades silently
|
|
5073
|
+
"Not published: this Claude Code version did not declare the elicitation capability, so the approve dialog cannot be shown. The gate never degrades silently - update Claude Code and try again.",
|
|
5068
5074
|
true
|
|
5069
5075
|
)
|
|
5070
5076
|
);
|
|
@@ -5138,13 +5144,15 @@ function createSyncServer(deps, send) {
|
|
|
5138
5144
|
(res) => {
|
|
5139
5145
|
if (staged?.id === approvedStage.id) staged = null;
|
|
5140
5146
|
const lines2 = [
|
|
5141
|
-
`Published. Snapshot received
|
|
5147
|
+
`Published. Snapshot received ${fmtReceivedAt(res.receivedAt)}.`,
|
|
5148
|
+
"",
|
|
5149
|
+
"Your stack now shows what actually ran:",
|
|
5142
5150
|
res.url
|
|
5143
5151
|
];
|
|
5144
5152
|
const kp = approvedStage.body.keptPrivate;
|
|
5145
5153
|
if (res.keptPrivate.refused && kp !== void 0) {
|
|
5146
5154
|
lines2.push(
|
|
5147
|
-
"Note: the kept-private names
|
|
5155
|
+
"Note: the server refused the kept-private names because its review switch is off. They stayed on this machine."
|
|
5148
5156
|
);
|
|
5149
5157
|
} else if (res.keptPrivate.stored > 0) {
|
|
5150
5158
|
lines2.push(
|