@illuminis/comprism 0.1.3 → 0.1.5
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 +61 -2
- package/out/agent/command.d.ts +75 -4
- package/out/agent/command.js +221 -26
- package/out/agent/render.d.ts +56 -8
- package/out/agent/render.js +441 -39
- package/out/agent/session.d.ts +156 -2
- package/out/agent/session.js +795 -154
- package/out/commands/agents.d.ts +2 -0
- package/out/commands/agents.js +79 -0
- package/out/commands/ask.d.ts +1 -1
- package/out/commands/ask.js +88 -12
- package/out/commands/commands-thin.js +79 -17
- package/out/commands/config.d.ts +1 -0
- package/out/commands/config.js +138 -0
- package/out/commands/cost.d.ts +1 -0
- package/out/commands/cost.js +167 -0
- package/out/commands/hooks.d.ts +1 -0
- package/out/commands/hooks.js +83 -0
- package/out/commands/install.d.ts +44 -1
- package/out/commands/install.js +198 -4
- package/out/commands/instructions.d.ts +1 -0
- package/out/commands/instructions.js +113 -0
- package/out/commands/integrations.d.ts +3 -0
- package/out/commands/integrations.js +215 -0
- package/out/commands/jobs.d.ts +5 -0
- package/out/commands/jobs.js +157 -0
- package/out/commands/login.js +249 -43
- package/out/commands/memory.d.ts +3 -0
- package/out/commands/memory.js +113 -0
- package/out/commands/permissions.d.ts +1 -0
- package/out/commands/permissions.js +94 -0
- package/out/commands/plugins.d.ts +4 -0
- package/out/commands/plugins.js +192 -0
- package/out/commands/privacy.d.ts +1 -0
- package/out/commands/privacy.js +57 -0
- package/out/commands/providerKey.d.ts +32 -0
- package/out/commands/providerKey.js +108 -0
- package/out/commands/repl.d.ts +12 -1
- package/out/commands/repl.js +1649 -122
- package/out/commands/report.d.ts +39 -0
- package/out/commands/report.js +115 -0
- package/out/commands/review.d.ts +5 -0
- package/out/commands/review.js +223 -0
- package/out/commands/sessions.d.ts +23 -0
- package/out/commands/sessions.js +115 -0
- package/out/commands/settings.d.ts +3 -1
- package/out/commands/settings.js +18 -16
- package/out/commands/skills.d.ts +21 -0
- package/out/commands/skills.js +207 -0
- package/out/commands/unattended.d.ts +7 -0
- package/out/commands/unattended.js +351 -0
- package/out/commands/update.d.ts +1 -0
- package/out/commands/update.js +123 -0
- package/out/commands/welcome.d.ts +25 -1
- package/out/commands/welcome.js +71 -35
- package/out/commands/worktrees.d.ts +5 -0
- package/out/commands/worktrees.js +186 -0
- package/out/executor/browser.d.ts +14 -0
- package/out/executor/browser.js +270 -0
- package/out/executor/diagnostics.d.ts +2 -0
- package/out/executor/diagnostics.js +181 -0
- package/out/executor/files.js +270 -40
- package/out/executor/git.js +42 -29
- package/out/executor/hooks.d.ts +42 -58
- package/out/executor/hooks.js +89 -182
- package/out/executor/index.d.ts +21 -5
- package/out/executor/index.js +160 -14
- package/out/executor/paths.d.ts +6 -1
- package/out/executor/paths.js +34 -6
- package/out/executor/sandbox.d.ts +40 -0
- package/out/executor/sandbox.js +299 -0
- package/out/executor/shell.d.ts +49 -4
- package/out/executor/shell.js +302 -56
- package/out/executor/toolservers.d.ts +20 -0
- package/out/executor/toolservers.js +189 -0
- package/out/executor/worktree.d.ts +9 -0
- package/out/executor/worktree.js +119 -0
- package/out/graph/read-python.js +2 -1
- package/out/lib/attach.d.ts +56 -12
- package/out/lib/attach.js +230 -63
- package/out/lib/clipboard.d.ts +23 -0
- package/out/lib/clipboard.js +182 -0
- package/out/lib/commandlist.d.ts +20 -0
- package/out/lib/commandlist.js +58 -0
- package/out/lib/decision.d.ts +22 -0
- package/out/lib/decision.js +50 -0
- package/out/lib/fingerprint.d.ts +25 -0
- package/out/lib/fingerprint.js +58 -0
- package/out/lib/gateway.d.ts +190 -2
- package/out/lib/gateway.js +59 -4
- package/out/lib/history.d.ts +24 -0
- package/out/lib/history.js +137 -0
- package/out/lib/ide.d.ts +19 -0
- package/out/lib/ide.js +131 -0
- package/out/lib/keyboard.d.ts +98 -0
- package/out/lib/keyboard.js +390 -0
- package/out/lib/lastlogin.d.ts +14 -0
- package/out/lib/lastlogin.js +105 -0
- package/out/lib/machine.d.ts +21 -0
- package/out/lib/machine.js +91 -0
- package/out/lib/notify.d.ts +4 -0
- package/out/lib/notify.js +52 -0
- package/out/lib/output.d.ts +48 -0
- package/out/lib/output.js +108 -0
- package/out/lib/picker.d.ts +1 -0
- package/out/lib/picker.js +158 -0
- package/out/lib/project-ops.d.ts +19 -0
- package/out/lib/project-ops.js +146 -0
- package/out/lib/project.d.ts +28 -0
- package/out/lib/project.js +114 -0
- package/out/lib/prompt.d.ts +3 -0
- package/out/lib/prompt.js +36 -2
- package/out/lib/queue.d.ts +13 -0
- package/out/lib/queue.js +116 -0
- package/out/lib/readiness.d.ts +19 -0
- package/out/lib/readiness.js +170 -1
- package/out/lib/self.d.ts +23 -0
- package/out/lib/self.js +124 -0
- package/out/lib/sessions.d.ts +19 -0
- package/out/lib/sessions.js +221 -0
- package/out/lib/startcache.d.ts +13 -0
- package/out/lib/startcache.js +78 -0
- package/out/lib/stdin.d.ts +32 -0
- package/out/lib/stdin.js +117 -0
- package/out/lib/store.d.ts +40 -0
- package/out/lib/store.js +138 -0
- package/out/lib/sync.d.ts +18 -0
- package/out/lib/sync.js +81 -0
- package/out/lib/ui.d.ts +10 -4
- package/out/lib/ui.js +65 -25
- package/out/lib/voice.d.ts +36 -0
- package/out/lib/voice.js +160 -1
- package/out/native/dictate +0 -0
- package/out/postinstall.js +42 -17
- package/out/providers/anthropic.d.ts +22 -0
- package/out/providers/anthropic.js +80 -0
- package/out/providers/index.d.ts +8 -0
- package/out/providers/index.js +83 -0
- package/out/providers/openai.d.ts +11 -0
- package/out/providers/openai.js +57 -0
- package/out/thin.js +595 -33
- package/package.json +11 -50
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface Bucket {
|
|
2
|
+
key: string;
|
|
3
|
+
calls: number;
|
|
4
|
+
inputTokens: number;
|
|
5
|
+
outputTokens: number;
|
|
6
|
+
costUsd: number;
|
|
7
|
+
unpriced: number;
|
|
8
|
+
/**
|
|
9
|
+
* The weakest provenance among the rows in this bucket, in words.
|
|
10
|
+
*
|
|
11
|
+
* Derived, never asserted. The report used to print "observed, first party"
|
|
12
|
+
* on every line regardless of what the rows actually said, which meant the
|
|
13
|
+
* desktop connector's inferred, unpriced turns were displayed as measured
|
|
14
|
+
* spend. A product whose whole claim is "you can see where this number came
|
|
15
|
+
* from" cannot print a provenance it did not read off the rows.
|
|
16
|
+
*/
|
|
17
|
+
provenance: string;
|
|
18
|
+
}
|
|
19
|
+
export interface Report {
|
|
20
|
+
since: Date | null;
|
|
21
|
+
rows: number;
|
|
22
|
+
totalCostUsd: number;
|
|
23
|
+
unpricedCalls: number;
|
|
24
|
+
totalInputTokens: number;
|
|
25
|
+
totalOutputTokens: number;
|
|
26
|
+
byModel: Bucket[];
|
|
27
|
+
byTier: Bucket[];
|
|
28
|
+
bySurface: Bucket[];
|
|
29
|
+
sessions: number;
|
|
30
|
+
/** Calls at attempt_index > 0: work that had to be done twice. */
|
|
31
|
+
remediationCalls: number;
|
|
32
|
+
remediationCostUsd: number;
|
|
33
|
+
/** Repeat attempts the provider never priced. Counted, never summed as zero. */
|
|
34
|
+
remediationUnpriced: number;
|
|
35
|
+
firstTs: string | null;
|
|
36
|
+
lastTs: string | null;
|
|
37
|
+
}
|
|
38
|
+
export declare function parseSince(spec: string | undefined): Date | null;
|
|
39
|
+
export declare function buildReport(since: Date | null): Report;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseSince = parseSince;
|
|
4
|
+
exports.buildReport = buildReport;
|
|
5
|
+
/**
|
|
6
|
+
* Spend reporting, computed from the ledger and nothing else.
|
|
7
|
+
*
|
|
8
|
+
* Every figure below is a sum over rows that were written once and never edited,
|
|
9
|
+
* so `report` can be reconciled against the provider's own invoice line by line.
|
|
10
|
+
* That reconciliation is the acceptance test for this file, and it is also the
|
|
11
|
+
* first thing a finance team will ask for.
|
|
12
|
+
*
|
|
13
|
+
* Rows the provider never priced are counted on their own line rather than
|
|
14
|
+
* folded into the total. A total that quietly includes zeros for calls of
|
|
15
|
+
* unknown cost is exactly the kind of figure this product exists to replace.
|
|
16
|
+
*/
|
|
17
|
+
const store_1 = require("../lib/store");
|
|
18
|
+
function parseSince(spec) {
|
|
19
|
+
if (!spec)
|
|
20
|
+
return null;
|
|
21
|
+
const m = /^(\d+)([dhw])$/.exec(spec.trim());
|
|
22
|
+
if (!m)
|
|
23
|
+
return null;
|
|
24
|
+
const n = Number(m[1]);
|
|
25
|
+
const unit = m[2];
|
|
26
|
+
const ms = unit === 'h' ? 3_600_000 : unit === 'w' ? 604_800_000 : 86_400_000;
|
|
27
|
+
return new Date(Date.now() - n * ms);
|
|
28
|
+
}
|
|
29
|
+
function bucketise(rows, keyOf) {
|
|
30
|
+
const map = new Map();
|
|
31
|
+
const derivations = new Map();
|
|
32
|
+
const provenances = new Map();
|
|
33
|
+
for (const r of rows) {
|
|
34
|
+
const key = keyOf(r);
|
|
35
|
+
const b = map.get(key) || {
|
|
36
|
+
key,
|
|
37
|
+
calls: 0,
|
|
38
|
+
inputTokens: 0,
|
|
39
|
+
outputTokens: 0,
|
|
40
|
+
costUsd: 0,
|
|
41
|
+
unpriced: 0,
|
|
42
|
+
provenance: '',
|
|
43
|
+
};
|
|
44
|
+
b.calls += 1;
|
|
45
|
+
b.inputTokens += r.input_tokens ?? 0;
|
|
46
|
+
b.outputTokens += r.output_tokens ?? 0;
|
|
47
|
+
if (r.cost_usd === null)
|
|
48
|
+
b.unpriced += 1;
|
|
49
|
+
else
|
|
50
|
+
b.costUsd += r.cost_usd;
|
|
51
|
+
map.set(key, b);
|
|
52
|
+
const seen = derivations.get(key) || new Set();
|
|
53
|
+
seen.add(r.meta?.derivation || 'observed');
|
|
54
|
+
derivations.set(key, seen);
|
|
55
|
+
const sources = provenances.get(key) || new Set();
|
|
56
|
+
sources.add(r.meta?.provenance || 'first_party');
|
|
57
|
+
provenances.set(key, sources);
|
|
58
|
+
}
|
|
59
|
+
for (const b of map.values()) {
|
|
60
|
+
b.provenance = describe(derivations.get(b.key) || new Set(), provenances.get(b.key) || new Set(), b.unpriced, b.calls);
|
|
61
|
+
}
|
|
62
|
+
return [...map.values()].sort((a, b) => b.costUsd - a.costUsd);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Say what a bucket's figures actually rest on.
|
|
66
|
+
*
|
|
67
|
+
* A displayed figure inherits the WEAKEST provenance among its inputs, so a
|
|
68
|
+
* bucket mixing observed and inferred rows is described as mixed, not as
|
|
69
|
+
* observed. And a bucket where nothing was priced says so in place of a cost,
|
|
70
|
+
* because "$0.00" and "we could not price this" are different facts that look
|
|
71
|
+
* identical on a screen.
|
|
72
|
+
*/
|
|
73
|
+
function describe(derivations, provenances, unpriced, calls) {
|
|
74
|
+
const source = provenances.has('imported')
|
|
75
|
+
? provenances.size > 1
|
|
76
|
+
? 'mixed source'
|
|
77
|
+
: 'imported'
|
|
78
|
+
: 'first party';
|
|
79
|
+
let strength;
|
|
80
|
+
if (derivations.size > 1)
|
|
81
|
+
strength = 'mixed';
|
|
82
|
+
else if (derivations.has('estimated'))
|
|
83
|
+
strength = 'estimated';
|
|
84
|
+
else if (derivations.has('inferred'))
|
|
85
|
+
strength = 'inferred';
|
|
86
|
+
else
|
|
87
|
+
strength = 'observed';
|
|
88
|
+
if (unpriced === calls && calls > 0)
|
|
89
|
+
return `${strength}, ${source}, none priced`;
|
|
90
|
+
if (unpriced > 0)
|
|
91
|
+
return `${strength}, ${source}, ${unpriced} unpriced`;
|
|
92
|
+
return `${strength}, ${source}`;
|
|
93
|
+
}
|
|
94
|
+
function buildReport(since) {
|
|
95
|
+
const all = (0, store_1.readRows)(store_1.LEDGER_FILE);
|
|
96
|
+
const rows = since ? all.filter((r) => new Date(r.ts) >= since) : all;
|
|
97
|
+
const remediation = rows.filter((r) => r.attempt_index > 0);
|
|
98
|
+
return {
|
|
99
|
+
since,
|
|
100
|
+
rows: rows.length,
|
|
101
|
+
totalCostUsd: rows.reduce((s, r) => s + (r.cost_usd ?? 0), 0),
|
|
102
|
+
unpricedCalls: rows.filter((r) => r.cost_usd === null).length,
|
|
103
|
+
totalInputTokens: rows.reduce((s, r) => s + (r.input_tokens ?? 0), 0),
|
|
104
|
+
totalOutputTokens: rows.reduce((s, r) => s + (r.output_tokens ?? 0), 0),
|
|
105
|
+
byModel: bucketise(rows, (r) => r.model_dispatched),
|
|
106
|
+
byTier: bucketise(rows, (r) => r.task_tier),
|
|
107
|
+
bySurface: bucketise(rows, (r) => r.source_system),
|
|
108
|
+
sessions: new Set(rows.map((r) => r.session_id)).size,
|
|
109
|
+
remediationCalls: remediation.length,
|
|
110
|
+
remediationCostUsd: remediation.reduce((s, r) => s + (r.cost_usd ?? 0), 0),
|
|
111
|
+
remediationUnpriced: remediation.filter((r) => r.cost_usd === null).length,
|
|
112
|
+
firstTs: rows[0]?.ts ?? null,
|
|
113
|
+
lastTs: rows[rows.length - 1]?.ts ?? null,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function review(op: string, payload: Record<string, unknown>, cwd?: string): Promise<import("../lib/gateway").GatewayReply>;
|
|
2
|
+
export declare function cmdChanges(args: string[]): Promise<number>;
|
|
3
|
+
export declare function cmdDiff(args: string[]): Promise<number>;
|
|
4
|
+
/** `confirmWith`: a session's own keyboard, so two readers never share it. */
|
|
5
|
+
export declare function cmdRewind(args: string[], confirmWith?: (question: string) => Promise<boolean>): Promise<number>;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.review = review;
|
|
37
|
+
exports.cmdChanges = cmdChanges;
|
|
38
|
+
exports.cmdDiff = cmdDiff;
|
|
39
|
+
exports.cmdRewind = cmdRewind;
|
|
40
|
+
/**
|
|
41
|
+
* `comprism changes`, `comprism diff` and `comprism rewind` (manual 7.1 to 7.4,
|
|
42
|
+
* 7.7).
|
|
43
|
+
*
|
|
44
|
+
* The service knows what the agent changed, from the job's own records and its
|
|
45
|
+
* checkpoints, and decides what putting a file back means: restore, delete or
|
|
46
|
+
* rename back, and what cannot be undone. This file prints its words, and does
|
|
47
|
+
* the physical part of a rewind: before writing each file it checks the file
|
|
48
|
+
* still holds what the agent left, and skips it otherwise unless `--force`.
|
|
49
|
+
*/
|
|
50
|
+
const crypto = __importStar(require("crypto"));
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
const ui = __importStar(require("../lib/ui"));
|
|
54
|
+
const gateway_1 = require("../lib/gateway");
|
|
55
|
+
const project_1 = require("../lib/project");
|
|
56
|
+
const project_2 = require("../lib/project");
|
|
57
|
+
const out = (s = "") => process.stdout.write(`${s}\n`);
|
|
58
|
+
const err = (s = "") => process.stderr.write(`${s}\n`);
|
|
59
|
+
async function review(op, payload, cwd = process.cwd()) {
|
|
60
|
+
const project = (0, project_1.resolveProject)(cwd);
|
|
61
|
+
return (0, gateway_1.ask)({ intent: "review", prompt: "", op, payload: { ...(0, project_2.asWire)(project), ...payload } });
|
|
62
|
+
}
|
|
63
|
+
function refused(reply) {
|
|
64
|
+
if (!reply.ok)
|
|
65
|
+
return reply.message || "Your workspace could not be reached.";
|
|
66
|
+
if (reply.served === false)
|
|
67
|
+
return "Your workspace is running an older build that does not answer this yet.";
|
|
68
|
+
if (reply.granted === false)
|
|
69
|
+
return reply.message || "Refused.";
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function words(args, valueFlags) {
|
|
73
|
+
return args.filter((a, i) => !a.startsWith("--") && !valueFlags.includes(String(args[i - 1])));
|
|
74
|
+
}
|
|
75
|
+
async function cmdChanges(args) {
|
|
76
|
+
const [job] = words(args, []);
|
|
77
|
+
const reply = await review("changes", { job: job ?? "" });
|
|
78
|
+
const why = refused(reply);
|
|
79
|
+
if (why) {
|
|
80
|
+
err(ui.fail(why));
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
out();
|
|
84
|
+
for (const line of reply.lines ?? [])
|
|
85
|
+
out(` ${line}`);
|
|
86
|
+
out();
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
async function cmdDiff(args) {
|
|
90
|
+
const [first, second] = words(args, []);
|
|
91
|
+
// `diff calc.py` is a file of the latest job; `diff j_7c21 calc.py` names both.
|
|
92
|
+
const isJob = first && !second && !first.includes(".") && !first.includes("/") ? true : Boolean(second);
|
|
93
|
+
const reply = await review("diff", { job: isJob ? first : "", file: isJob ? (second ?? "") : (first ?? "") });
|
|
94
|
+
const why = refused(reply);
|
|
95
|
+
if (why) {
|
|
96
|
+
err(ui.fail(why));
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
out();
|
|
100
|
+
for (const line of reply.lines ?? []) {
|
|
101
|
+
const shown = line.startsWith("+") && !line.startsWith("+++") ? ui.c.green(line)
|
|
102
|
+
: line.startsWith("-") && !line.startsWith("---") ? ui.c.red(line) : line;
|
|
103
|
+
out(` ${shown}`);
|
|
104
|
+
}
|
|
105
|
+
out();
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
function sha(file) {
|
|
109
|
+
try {
|
|
110
|
+
return { exists: true, sha: crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex") };
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return { exists: false, sha: null };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function inside(root, rel) {
|
|
117
|
+
const base = fs.realpathSync(root);
|
|
118
|
+
const where = path.resolve(base, rel);
|
|
119
|
+
return where.startsWith(base + path.sep) ? where : null;
|
|
120
|
+
}
|
|
121
|
+
async function confirm(question) {
|
|
122
|
+
if (!process.stdin.isTTY)
|
|
123
|
+
return false;
|
|
124
|
+
const { ask: prompt, closePrompt } = await Promise.resolve().then(() => __importStar(require("../lib/prompt")));
|
|
125
|
+
const answer = await prompt(` ${question} `);
|
|
126
|
+
closePrompt();
|
|
127
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
128
|
+
}
|
|
129
|
+
/** `confirmWith`: a session's own keyboard, so two readers never share it. */
|
|
130
|
+
async function cmdRewind(args, confirmWith) {
|
|
131
|
+
const force = args.includes("--force");
|
|
132
|
+
const yes = args.includes("--yes");
|
|
133
|
+
const stepAt = args.indexOf("--to-step");
|
|
134
|
+
const toStep = stepAt === -1 ? null : Number(args[stepAt + 1]);
|
|
135
|
+
if (stepAt !== -1 && (!Number.isInteger(toStep) || (toStep ?? -1) < 0)) {
|
|
136
|
+
err(ui.fail("--to-step takes a step number, for example --to-step 2."));
|
|
137
|
+
return 2;
|
|
138
|
+
}
|
|
139
|
+
const [job, file] = words(args, ["--to-step"]);
|
|
140
|
+
const project = (0, project_1.resolveProject)(process.cwd());
|
|
141
|
+
const reply = await review("rewind_plan", { job: job ?? "", to_step: toStep, file: file ?? "" });
|
|
142
|
+
const why = refused(reply);
|
|
143
|
+
if (why) {
|
|
144
|
+
err(ui.fail(why));
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
const data = (reply.data ?? {});
|
|
148
|
+
const items = data.items ?? [];
|
|
149
|
+
out();
|
|
150
|
+
for (const line of reply.lines ?? [])
|
|
151
|
+
out(` ${line}`);
|
|
152
|
+
if (!items.length) {
|
|
153
|
+
out();
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
if (!yes && !(await (confirmWith ?? confirm)("Go ahead? [y/N]"))) {
|
|
157
|
+
out(ui.c.dim(" Nothing was changed."));
|
|
158
|
+
out();
|
|
159
|
+
return process.stdin.isTTY ? 0 : 4;
|
|
160
|
+
}
|
|
161
|
+
const results = [];
|
|
162
|
+
for (const item of items) {
|
|
163
|
+
const where = inside(project.root, item.path);
|
|
164
|
+
if (!where) {
|
|
165
|
+
results.push({ path: item.path, result: "outside this project, not restored" });
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Your later changes are never overwritten (7.4): the file must still be
|
|
169
|
+
// what the agent left, or it is skipped unless --force.
|
|
170
|
+
const now = sha(where);
|
|
171
|
+
const agentLeft = item.expected_exists === false ? { exists: false, sha: null }
|
|
172
|
+
: { exists: true, sha: item.expected_sha ?? null };
|
|
173
|
+
const same = now.exists === agentLeft.exists && (!now.exists || !agentLeft.sha || now.sha === agentLeft.sha);
|
|
174
|
+
if (!same && !force) {
|
|
175
|
+
results.push({ path: item.path, result: "changed since the job, not restored (--force restores it)" });
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (!same && force && now.exists) {
|
|
179
|
+
out(ui.c.amber(` --force: ${item.path} was changed since the job; those changes are lost:`));
|
|
180
|
+
const current = fs.readFileSync(where, "utf8").split("\n").slice(0, 20);
|
|
181
|
+
for (const l of current)
|
|
182
|
+
out(ui.c.dim(` ${l}`));
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
if (item.action === "delete") {
|
|
186
|
+
if (now.exists)
|
|
187
|
+
fs.rmSync(where);
|
|
188
|
+
results.push({ path: item.path, result: "deleted (created by the agent)" });
|
|
189
|
+
}
|
|
190
|
+
else if (item.action === "rename_back" && item.to) {
|
|
191
|
+
const from = inside(project.root, item.to);
|
|
192
|
+
if (!from)
|
|
193
|
+
throw new Error("the new name is outside this project");
|
|
194
|
+
fs.mkdirSync(path.dirname(where), { recursive: true });
|
|
195
|
+
if (fs.existsSync(from))
|
|
196
|
+
fs.renameSync(from, where);
|
|
197
|
+
if (typeof item.content === "string")
|
|
198
|
+
fs.writeFileSync(where, item.content);
|
|
199
|
+
results.push({ path: item.path, result: `renamed back from ${item.to}` });
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
if (typeof item.content !== "string")
|
|
203
|
+
throw new Error("no copy was kept");
|
|
204
|
+
fs.mkdirSync(path.dirname(where), { recursive: true });
|
|
205
|
+
fs.writeFileSync(where, item.content);
|
|
206
|
+
results.push({ path: item.path, result: "restored" });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
results.push({ path: item.path, result: `not restored: ${e.message}` });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const ok = results.filter((r) => /^(restored|deleted|renamed back)/.test(r.result));
|
|
214
|
+
out();
|
|
215
|
+
for (const r of results)
|
|
216
|
+
out(` ${ok.includes(r) ? ui.c.green("done") : ui.c.amber("left")} ${r.path} ${r.result}`);
|
|
217
|
+
out(ok.length === results.length
|
|
218
|
+
? ui.ok(`Rewound ${ok.length} file${ok.length === 1 ? "" : "s"}.`)
|
|
219
|
+
: ui.warn(`Rewound ${ok.length} of ${results.length} files. The others were left as they are.`));
|
|
220
|
+
out();
|
|
221
|
+
await review("rewound", { job_id: data.job_id, to_step: toStep, results });
|
|
222
|
+
return ok.length === results.length ? 0 : 1;
|
|
223
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Message, PersistedTurn, Provider, Session, SourceSurface } from '../lib/types';
|
|
2
|
+
export declare function listSessions(): Session[];
|
|
3
|
+
export declare function getSession(sessionId: string): Session | undefined;
|
|
4
|
+
export declare function newSession(title?: string, surface?: SourceSurface): Session;
|
|
5
|
+
/** Context size in tokens, from what has already been persisted. */
|
|
6
|
+
export declare function contextTokens(session: Session): number;
|
|
7
|
+
export declare function isFresh(session: Session): boolean;
|
|
8
|
+
export declare function appendTurn(sessionId: string, role: 'user' | 'assistant', content: string, meta?: {
|
|
9
|
+
model?: string;
|
|
10
|
+
provider?: Provider;
|
|
11
|
+
}): PersistedTurn;
|
|
12
|
+
/**
|
|
13
|
+
* Rebuild the message history a provider needs.
|
|
14
|
+
*
|
|
15
|
+
* Only possible when text storage is on. Otherwise the honest answer is an
|
|
16
|
+
* empty history and a caller that says so - being content-blind has a cost, and
|
|
17
|
+
* paying it visibly beats storing the text and calling the product
|
|
18
|
+
* content-blind anyway.
|
|
19
|
+
*/
|
|
20
|
+
export declare function replayableHistory(session: Session): {
|
|
21
|
+
messages: Message[];
|
|
22
|
+
replayable: boolean;
|
|
23
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.listSessions = listSessions;
|
|
7
|
+
exports.getSession = getSession;
|
|
8
|
+
exports.newSession = newSession;
|
|
9
|
+
exports.contextTokens = contextTokens;
|
|
10
|
+
exports.isFresh = isFresh;
|
|
11
|
+
exports.appendTurn = appendTurn;
|
|
12
|
+
exports.replayableHistory = replayableHistory;
|
|
13
|
+
/**
|
|
14
|
+
* Work sessions: a person's working conversation, recorded content-blind.
|
|
15
|
+
*
|
|
16
|
+
* `sessions.json` is the one store here that is rewritten rather than appended.
|
|
17
|
+
* That is deliberate and it is not a hole in the append-only rule: a session is
|
|
18
|
+
* current state (a title, a turn count, a context size), not evidence. Every
|
|
19
|
+
* evidentiary claim - what was dispatched, what it cost, what was decided -
|
|
20
|
+
* lives in the ledger and the decision records, which are never rewritten.
|
|
21
|
+
*
|
|
22
|
+
* What is persisted per turn is the SHAPE of the turn: role, fingerprint, size,
|
|
23
|
+
* and which model answered. Not its words, unless the tenant has explicitly
|
|
24
|
+
* turned text storage on.
|
|
25
|
+
*/
|
|
26
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
27
|
+
const config_1 = require("../lib/config");
|
|
28
|
+
const fingerprint_1 = require("../lib/fingerprint");
|
|
29
|
+
const store_1 = require("../lib/store");
|
|
30
|
+
function readAll() {
|
|
31
|
+
const p = (0, store_1.storePath)(store_1.SESSIONS_FILE);
|
|
32
|
+
if (!node_fs_1.default.existsSync(p))
|
|
33
|
+
return {};
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(node_fs_1.default.readFileSync(p, 'utf8'));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// A corrupt session index must not stop someone working. Start clean; the
|
|
39
|
+
// ledger and the decision records are the parts that matter and they are
|
|
40
|
+
// append-only, so nothing evidentiary is lost here.
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function writeAll(map) {
|
|
45
|
+
(0, config_1.ensureHome)();
|
|
46
|
+
node_fs_1.default.writeFileSync((0, store_1.storePath)(store_1.SESSIONS_FILE), `${JSON.stringify(map, null, 2)}\n`, { mode: 0o600 });
|
|
47
|
+
}
|
|
48
|
+
function listSessions() {
|
|
49
|
+
return Object.values(readAll()).sort((a, b) => b.updated.localeCompare(a.updated));
|
|
50
|
+
}
|
|
51
|
+
function getSession(sessionId) {
|
|
52
|
+
return readAll()[sessionId];
|
|
53
|
+
}
|
|
54
|
+
function newSession(title, surface = 'sdk') {
|
|
55
|
+
const now = new Date().toISOString();
|
|
56
|
+
const session = {
|
|
57
|
+
session_id: (0, fingerprint_1.newId)('ses'),
|
|
58
|
+
title: title || 'Untitled session',
|
|
59
|
+
created: now,
|
|
60
|
+
updated: now,
|
|
61
|
+
source_surface: surface,
|
|
62
|
+
turns: [],
|
|
63
|
+
};
|
|
64
|
+
const map = readAll();
|
|
65
|
+
map[session.session_id] = session;
|
|
66
|
+
writeAll(map);
|
|
67
|
+
return session;
|
|
68
|
+
}
|
|
69
|
+
/** Context size in tokens, from what has already been persisted. */
|
|
70
|
+
function contextTokens(session) {
|
|
71
|
+
return session.turns.reduce((sum, t) => sum + t.approx_tokens, 0);
|
|
72
|
+
}
|
|
73
|
+
function isFresh(session) {
|
|
74
|
+
return session.turns.length === 0;
|
|
75
|
+
}
|
|
76
|
+
function appendTurn(sessionId, role, content, meta = {}) {
|
|
77
|
+
const cfg = (0, config_1.loadConfig)();
|
|
78
|
+
const turn = {
|
|
79
|
+
role,
|
|
80
|
+
ts: new Date().toISOString(),
|
|
81
|
+
fingerprint: (0, fingerprint_1.fingerprint)(content),
|
|
82
|
+
chars: content.length,
|
|
83
|
+
approx_tokens: (0, fingerprint_1.approxTokens)(content),
|
|
84
|
+
...(meta.model ? { model: meta.model } : {}),
|
|
85
|
+
...(meta.provider ? { provider: meta.provider } : {}),
|
|
86
|
+
// Off by default. The whole product is built to work without it.
|
|
87
|
+
...(cfg.contentPolicy.storeText ? { content } : {}),
|
|
88
|
+
};
|
|
89
|
+
const map = readAll();
|
|
90
|
+
const session = map[sessionId];
|
|
91
|
+
if (!session)
|
|
92
|
+
throw new Error(`unknown session: ${sessionId}`);
|
|
93
|
+
session.turns.push(turn);
|
|
94
|
+
session.updated = turn.ts;
|
|
95
|
+
writeAll(map);
|
|
96
|
+
return turn;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Rebuild the message history a provider needs.
|
|
100
|
+
*
|
|
101
|
+
* Only possible when text storage is on. Otherwise the honest answer is an
|
|
102
|
+
* empty history and a caller that says so - being content-blind has a cost, and
|
|
103
|
+
* paying it visibly beats storing the text and calling the product
|
|
104
|
+
* content-blind anyway.
|
|
105
|
+
*/
|
|
106
|
+
function replayableHistory(session) {
|
|
107
|
+
const withText = session.turns.filter((t) => typeof t.content === 'string');
|
|
108
|
+
if (session.turns.length > 0 && withText.length !== session.turns.length) {
|
|
109
|
+
return { messages: [], replayable: false };
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
messages: withText.map((t) => ({ role: t.role, content: t.content })),
|
|
113
|
+
replayable: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -18,4 +18,6 @@ export declare function cmdProviders(): Promise<void>;
|
|
|
18
18
|
* have used, which is the only thing a saving can honestly be measured against.
|
|
19
19
|
*/
|
|
20
20
|
export declare function cmdModel(args: string[]): Promise<void>;
|
|
21
|
-
export declare function
|
|
21
|
+
export declare function cmdProject(args: string[]): Promise<void>;
|
|
22
|
+
/** The older name, kept so nothing that calls it breaks. */
|
|
23
|
+
export declare const cmdWorkspace: typeof cmdProject;
|
package/out/commands/settings.js
CHANGED
|
@@ -36,10 +36,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.cmdWorkspace = void 0;
|
|
39
40
|
exports.currentWorkspace = currentWorkspace;
|
|
40
41
|
exports.cmdProviders = cmdProviders;
|
|
41
42
|
exports.cmdModel = cmdModel;
|
|
42
|
-
exports.
|
|
43
|
+
exports.cmdProject = cmdProject;
|
|
43
44
|
/**
|
|
44
45
|
* The three things a person sets once and then forgets: which model they are
|
|
45
46
|
* compared against, which vendors they can reach, and which folder they work in.
|
|
@@ -62,6 +63,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
62
63
|
const prompt_1 = require("../lib/prompt");
|
|
63
64
|
const gateway_1 = require("../lib/gateway");
|
|
64
65
|
const config_1 = require("../lib/config");
|
|
66
|
+
const project_1 = require("../lib/project");
|
|
65
67
|
const ui = __importStar(require("../lib/ui"));
|
|
66
68
|
const out = (s = '') => process.stdout.write(s + '\n');
|
|
67
69
|
/** Where the chosen folder is remembered. Beside the rest of the settings. */
|
|
@@ -193,10 +195,10 @@ async function cmdModel(args) {
|
|
|
193
195
|
out(` ${ui.c.dim('every receipt from now on prices your work against this model')}`);
|
|
194
196
|
out();
|
|
195
197
|
}
|
|
196
|
-
async function
|
|
198
|
+
async function cmdProject(args) {
|
|
197
199
|
const named = args.find((a) => !a.startsWith('--'));
|
|
198
200
|
const current = currentWorkspace();
|
|
199
|
-
out(ui.banner('
|
|
201
|
+
out(ui.banner('Project folder'));
|
|
200
202
|
out();
|
|
201
203
|
out(` ${ui.c.bold(current.path)}`);
|
|
202
204
|
out(` ${ui.c.dim(current.source === 'cwd'
|
|
@@ -212,8 +214,12 @@ async function cmdWorkspace(args) {
|
|
|
212
214
|
out();
|
|
213
215
|
return;
|
|
214
216
|
}
|
|
217
|
+
// `comprism project` on its own shows the folder (manual 2.1). Asking only
|
|
218
|
+
// when a person is at the keyboard and typed nothing, as before.
|
|
215
219
|
let target = named;
|
|
216
220
|
if (!target) {
|
|
221
|
+
if (!process.stdin.isTTY)
|
|
222
|
+
return;
|
|
217
223
|
const answer = await (0, prompt_1.ask)(' new folder, or enter to keep it: ');
|
|
218
224
|
(0, prompt_1.closePrompt)();
|
|
219
225
|
if (!answer) {
|
|
@@ -223,22 +229,18 @@ async function cmdWorkspace(args) {
|
|
|
223
229
|
}
|
|
224
230
|
target = answer;
|
|
225
231
|
}
|
|
226
|
-
const
|
|
227
|
-
if (!
|
|
228
|
-
out(ui.fail(
|
|
229
|
-
out();
|
|
230
|
-
process.exitCode = 1;
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
if (!node_fs_1.default.statSync(resolved).isDirectory()) {
|
|
234
|
-
out(ui.fail(`${resolved} is a file, not a folder.`));
|
|
232
|
+
const found = (0, project_1.checkFolder)(target);
|
|
233
|
+
if (!found.ok) {
|
|
234
|
+
out(ui.fail(found.why));
|
|
235
235
|
out();
|
|
236
|
-
process.exitCode =
|
|
236
|
+
process.exitCode = 2;
|
|
237
237
|
return;
|
|
238
238
|
}
|
|
239
239
|
node_fs_1.default.mkdirSync((0, config_1.homeDir)(), { recursive: true });
|
|
240
|
-
node_fs_1.default.writeFileSync(workspaceFile(), JSON.stringify({ path:
|
|
241
|
-
out(ui.ok(`
|
|
242
|
-
out(` ${ui.c.dim('clear it with')} ${ui.c.bold('comprism
|
|
240
|
+
node_fs_1.default.writeFileSync(workspaceFile(), JSON.stringify({ path: found.path }, null, 2), { mode: 0o600 });
|
|
241
|
+
out(ui.ok(`Project folder set to ${ui.c.bold(found.path)}.`));
|
|
242
|
+
out(` ${ui.c.dim('clear it with')} ${ui.c.bold('comprism project --clear')}`);
|
|
243
243
|
out();
|
|
244
244
|
}
|
|
245
|
+
/** The older name, kept so nothing that calls it breaks. */
|
|
246
|
+
exports.cmdWorkspace = cmdProject;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type Row = {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
where: string;
|
|
5
|
+
active: boolean;
|
|
6
|
+
note?: string | null;
|
|
7
|
+
};
|
|
8
|
+
type Checked = {
|
|
9
|
+
level: string;
|
|
10
|
+
name: string;
|
|
11
|
+
line: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function skillRows(folder: string, op?: "list" | "check"): Promise<{
|
|
14
|
+
rows: Row[];
|
|
15
|
+
checked?: Checked[];
|
|
16
|
+
empty?: string | null;
|
|
17
|
+
error?: string;
|
|
18
|
+
}>;
|
|
19
|
+
export declare const SKILLS_HELP = " comprism skills the skills here and where each came from\n comprism skills add --personal <folder> keep a skill folder on your account\n comprism skills remove --personal <name> remove one of your own skills\n comprism skills check what will not load, and how to fix it\n In a session, /name followed by anything runs that skill with it.";
|
|
20
|
+
export declare function cmdSkills(args: string[]): Promise<number>;
|
|
21
|
+
export {};
|