codehere 1.0.1 → 1.0.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 +36 -192
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -147
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `codehere team`, the CLI face of the local-first team cost+audit ledger.
|
|
3
|
-
*
|
|
4
|
-
* The data layer (config, snapshots, merge, git sync) lives in
|
|
5
|
-
* infrastructure/cost/team-ledger.ts so the CLI and the daemon (`GET /api/team`)
|
|
6
|
-
* read the same core. This file is just the terminal rendering + subcommands.
|
|
7
|
-
*
|
|
8
|
-
* Subcommands:
|
|
9
|
-
* (none) / sync write this machine's snapshot, sync the team repo, show merged view
|
|
10
|
-
* init [path] create a team: make + git-init a shared folder and point at it
|
|
11
|
-
* join <git-url> join a team: clone the shared repo and point at it
|
|
12
|
-
* set-dir <path> point at an existing shared team directory
|
|
13
|
-
* budget <usd> set the team's shared daily ceiling (off = clear)
|
|
14
|
-
*/
|
|
15
|
-
import { existsSync, mkdirSync } from 'fs';
|
|
16
|
-
import { execFileSync } from 'child_process';
|
|
17
|
-
import { join as pathJoin, basename } from 'path';
|
|
18
|
-
import { loadTeamConfig, saveTeamConfig, codehereHome, initTeamDir, isGitRepo, gitPull, gitShareMine, writeMyShare, buildTeamView, } from '../../../infrastructure/cost/team-ledger.js';
|
|
19
|
-
import { success, error, info, heading, newline } from '../../../ui.js';
|
|
20
|
-
function fmtUsd(n) {
|
|
21
|
-
return n >= 0.01 || n === 0 ? `$${n.toFixed(2)}` : `$${n.toFixed(3)}`;
|
|
22
|
-
}
|
|
23
|
-
export async function executeTeamCommand(subcommand, value, opts) {
|
|
24
|
-
const sub = subcommand ?? 'sync';
|
|
25
|
-
switch (sub) {
|
|
26
|
-
case 'sync':
|
|
27
|
-
case 'show':
|
|
28
|
-
return syncAndShow(Boolean(opts?.strict));
|
|
29
|
-
case 'init':
|
|
30
|
-
return initTeam(value);
|
|
31
|
-
case 'join':
|
|
32
|
-
if (!value) {
|
|
33
|
-
console.error(error('Usage:'), 'codehere team join <git-url>');
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
return joinTeam(value);
|
|
37
|
-
case 'set-dir':
|
|
38
|
-
if (!value) {
|
|
39
|
-
console.error(error('Usage:'), 'codehere team set-dir <path-to-shared-dir>');
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
return setDir(value);
|
|
43
|
-
case 'budget':
|
|
44
|
-
if (!value) {
|
|
45
|
-
console.error(error('Usage:'), 'codehere team budget <usd> (or `off`)');
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
return setBudget(value);
|
|
49
|
-
default:
|
|
50
|
-
console.error(error('Unknown subcommand:'), sub);
|
|
51
|
-
console.error(info('Try:'), 'codehere team [sync | init [path] | join <git-url> | set-dir <path> | budget <usd>]');
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function setDir(path) {
|
|
56
|
-
const cfg = loadTeamConfig();
|
|
57
|
-
cfg.dir = path;
|
|
58
|
-
saveTeamConfig(cfg);
|
|
59
|
-
console.log(success('Team directory set:'), path);
|
|
60
|
-
console.log(info('Next:'), 'run `codehere team` on each machine. Keep that folder in your own git/shared drive.');
|
|
61
|
-
}
|
|
62
|
-
// Create a team: make + git-init a shared folder, point at it, write the first
|
|
63
|
-
// snapshot — so a lead sets up a team in one command, no hand-editing config.
|
|
64
|
-
function initTeam(dir) {
|
|
65
|
-
const target = dir || pathJoin(codehereHome(), 'team-shared');
|
|
66
|
-
try {
|
|
67
|
-
initTeamDir(target); // shared core: mkdir + git-init + README (also used by POST /api/team/init)
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
console.error(error('Could not create team dir:'), target, '— check the path and permissions.');
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
const cfg = loadTeamConfig();
|
|
74
|
-
cfg.dir = target;
|
|
75
|
-
saveTeamConfig(cfg);
|
|
76
|
-
try {
|
|
77
|
-
writeMyShare(target);
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
/* best-effort first snapshot */
|
|
81
|
-
}
|
|
82
|
-
console.log(success('Team created:'), target);
|
|
83
|
-
console.log(info('Share it:'), 'push this folder to a git repo, then each teammate runs `codehere team join <git-url>`.');
|
|
84
|
-
}
|
|
85
|
-
// Join a team: clone the shared repo under ~/.codehere/teams/<name>, point at it,
|
|
86
|
-
// write this machine's first snapshot.
|
|
87
|
-
function joinTeam(url) {
|
|
88
|
-
// A join URL is shared/pasted, so it's a real attack surface. git's ext::/fd::
|
|
89
|
-
// (and file::) transports execute arbitrary commands, and a leading-dash URL is
|
|
90
|
-
// parsed as a git option (argument injection). Allow only real remote schemes.
|
|
91
|
-
if (/^-/.test(url) || /^(ext|fd|file)::/i.test(url) || !/^(https?:\/\/|git:\/\/|ssh:\/\/|git@[^/]+:)/.test(url)) {
|
|
92
|
-
console.error(error('Unsupported git URL:'), url);
|
|
93
|
-
console.error(info('Use:'), 'https:// , git:// , ssh:// , or git@host:owner/repo');
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
const name = basename(url.replace(/\.git$/, '')) || 'team-shared';
|
|
97
|
-
const target = pathJoin(codehereHome(), 'teams', name);
|
|
98
|
-
if (existsSync(target)) {
|
|
99
|
-
console.error(error('Already exists:'), target);
|
|
100
|
-
console.error(info('Point at it instead:'), `codehere team set-dir ${target}`);
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
mkdirSync(pathJoin(codehereHome(), 'teams'), { recursive: true });
|
|
105
|
-
// Belt-and-suspenders: disable the command-executing transports at the git
|
|
106
|
-
// level too, and `--` so the URL can never be read as an option.
|
|
107
|
-
execFileSync('git', ['-c', 'protocol.ext.allow=never', '-c', 'protocol.allow=user', 'clone', '-q', '--', url, target], { stdio: 'inherit' });
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
console.error(error('Clone failed:'), url, '— check the URL and your access.');
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
if (!existsSync(pathJoin(target, 'spend')))
|
|
114
|
-
mkdirSync(pathJoin(target, 'spend'), { recursive: true });
|
|
115
|
-
const cfg = loadTeamConfig();
|
|
116
|
-
cfg.dir = target;
|
|
117
|
-
saveTeamConfig(cfg);
|
|
118
|
-
try {
|
|
119
|
-
writeMyShare(target);
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
/* best-effort */
|
|
123
|
-
}
|
|
124
|
-
console.log(success('Joined team:'), target);
|
|
125
|
-
console.log(info('Next:'), 'run `codehere team` to sync and see the merged view.');
|
|
126
|
-
}
|
|
127
|
-
function setBudget(value) {
|
|
128
|
-
const cfg = loadTeamConfig();
|
|
129
|
-
if (value.toLowerCase() === 'off') {
|
|
130
|
-
cfg.dailyUsd = null;
|
|
131
|
-
saveTeamConfig(cfg);
|
|
132
|
-
console.log(success('Team daily budget cleared.'));
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const num = Number(value.replace(/^\$/, ''));
|
|
136
|
-
if (!Number.isFinite(num) || num <= 0) {
|
|
137
|
-
console.error(error('Invalid amount:'), value, '(use a positive number, e.g. `codehere team budget 50`)');
|
|
138
|
-
process.exit(1);
|
|
139
|
-
}
|
|
140
|
-
cfg.dailyUsd = num;
|
|
141
|
-
saveTeamConfig(cfg);
|
|
142
|
-
console.log(success('Team daily budget set:'), fmtUsd(num));
|
|
143
|
-
}
|
|
144
|
-
function syncAndShow(strict = false) {
|
|
145
|
-
const cfg = loadTeamConfig();
|
|
146
|
-
if (!cfg.dir) {
|
|
147
|
-
console.error(error('No team directory set.'));
|
|
148
|
-
console.error(info('Set one (a folder your team controls, e.g. a shared git repo):'));
|
|
149
|
-
console.error(' codehere team set-dir /path/to/team-shared-dir');
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
if (!existsSync(cfg.dir)) {
|
|
153
|
-
console.error(error('Team directory does not exist:'), cfg.dir);
|
|
154
|
-
console.error(info('Create it (or clone the team repo) first, then re-run.'));
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
const gitMode = isGitRepo(cfg.dir);
|
|
158
|
-
const pulled = gitMode ? gitPull(cfg.dir) : false;
|
|
159
|
-
const mine = writeMyShare(cfg.dir);
|
|
160
|
-
const pushed = gitMode ? gitShareMine(cfg.dir, `spend/${mine.machineId}.json`) : null;
|
|
161
|
-
const view = buildTeamView(cfg.dir, cfg.dailyUsd);
|
|
162
|
-
newline();
|
|
163
|
-
console.log(heading(`Team spend today (${view.date}), estimate, not a provider invoice`));
|
|
164
|
-
console.log(' ' + '─'.repeat(48));
|
|
165
|
-
for (const m of view.machines) {
|
|
166
|
-
const me = m.machineId === view.myMachineId ? ' (this machine)' : '';
|
|
167
|
-
console.log(` ${m.machineId.padEnd(28)} ${fmtUsd(m.totalUsd).padStart(9)}${me}`);
|
|
168
|
-
}
|
|
169
|
-
const agents = Object.entries(view.perAgent).sort((a, b) => b[1] - a[1]);
|
|
170
|
-
if (agents.length) {
|
|
171
|
-
console.log(' ' + '─'.repeat(48));
|
|
172
|
-
console.log(info(' by agent (cost · what each did)'));
|
|
173
|
-
for (const [agent, usd] of agents) {
|
|
174
|
-
const sess = view.perAgentSessions[agent] ?? 0;
|
|
175
|
-
console.log(` ${agent.padEnd(20)} ${fmtUsd(usd).padStart(9)} ${String(sess).padStart(3)} session${sess === 1 ? '' : 's'}`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
const projects = Object.entries(view.perProject).sort((a, b) => b[1] - a[1]);
|
|
179
|
-
if (projects.length) {
|
|
180
|
-
console.log(' ' + '─'.repeat(48));
|
|
181
|
-
console.log(info(' by project'));
|
|
182
|
-
for (const [proj, usd] of projects)
|
|
183
|
-
console.log(` ${proj.padEnd(28)} ${fmtUsd(usd).padStart(9)}`);
|
|
184
|
-
}
|
|
185
|
-
// Practice runs sit OUTSIDE the totals (their cost is the mock's own
|
|
186
|
-
// fabrication, never billed) — one quiet line, only when any exist.
|
|
187
|
-
if (view.practice && view.practice.sessions > 0) {
|
|
188
|
-
console.log(' ' + '─'.repeat(48));
|
|
189
|
-
console.log(info(` practice runs · ${view.practice.sessions} session${view.practice.sessions === 1 ? '' : 's'} · ${fmtUsd(view.practice.usd)} fabricated · not in the totals`));
|
|
190
|
-
}
|
|
191
|
-
console.log(' ' + '─'.repeat(48));
|
|
192
|
-
const totalLine = ` ${'TEAM TOTAL'.padEnd(28)} ${fmtUsd(view.teamTotal).padStart(9)}`;
|
|
193
|
-
if (view.dailyUsd !== null) {
|
|
194
|
-
const pct = Math.min(100, Math.round((view.teamTotal / view.dailyUsd) * 100));
|
|
195
|
-
console.log(totalLine + ` of ${fmtUsd(view.dailyUsd)} (${pct}%)`);
|
|
196
|
-
if (view.teamTotal >= view.dailyUsd)
|
|
197
|
-
console.log(error(' Over the team daily budget.'));
|
|
198
|
-
else if (pct >= 80)
|
|
199
|
-
console.log(info(' Approaching the team daily budget.'));
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
console.log(totalLine);
|
|
203
|
-
console.log(info(' No team budget set, `codehere team budget <usd>` to add one.'));
|
|
204
|
-
}
|
|
205
|
-
if (view.totalActions > 0) {
|
|
206
|
-
newline();
|
|
207
|
-
console.log(heading('What the agents did (recorded, integrity-checked)'));
|
|
208
|
-
console.log(' ' + '─'.repeat(48));
|
|
209
|
-
for (const m of view.machines.filter((x) => x.audit).sort((a, b) => b.audit.actions - a.audit.actions)) {
|
|
210
|
-
const status = m.audit.chainOk ? success('chain ok') : error('chain BROKEN');
|
|
211
|
-
console.log(` ${m.machineId.padEnd(28)} ${String(m.audit.actions).padStart(6)} actions ${status}`);
|
|
212
|
-
}
|
|
213
|
-
console.log(' ' + '─'.repeat(48));
|
|
214
|
-
console.log(` ${'TEAM ACTIONS'.padEnd(28)} ${String(view.totalActions).padStart(6)}`);
|
|
215
|
-
if (view.brokenChains.length)
|
|
216
|
-
console.log(error(` Integrity broken on: ${view.brokenChains.join(', ')}. Investigate before trusting that record.`));
|
|
217
|
-
}
|
|
218
|
-
if (view.tamperedShares.length || view.unsignedShares.length) {
|
|
219
|
-
newline();
|
|
220
|
-
if (view.tamperedShares.length)
|
|
221
|
-
console.log(error(` ✗ Signature/key mismatch on: ${view.tamperedShares.join(', ')} — altered, or re-signed with a different key than first seen. Don't trust that share.`));
|
|
222
|
-
if (view.unsignedShares.length)
|
|
223
|
-
console.log(info(` Unsigned, can't verify: ${view.unsignedShares.join(', ')}.`));
|
|
224
|
-
}
|
|
225
|
-
else if (view.machines.length) {
|
|
226
|
-
newline();
|
|
227
|
-
console.log(success(' ✓ All shares signed; keys match the ones first seen (TOFU) — no post-write change detected.'));
|
|
228
|
-
}
|
|
229
|
-
newline();
|
|
230
|
-
if (gitMode) {
|
|
231
|
-
const pushMsg = pushed === 'pushed'
|
|
232
|
-
? 'pushed yours'
|
|
233
|
-
: pushed === 'committed-local'
|
|
234
|
-
? 'committed yours locally (no remote or offline)'
|
|
235
|
-
: pushed === 'nochange'
|
|
236
|
-
? 'yours unchanged'
|
|
237
|
-
: 'could not push yours';
|
|
238
|
-
console.log(info('Synced:'), `git ${pulled ? 'pulled teammates' : 'pull skipped'}, ${pushMsg}. ${view.machines.length} machine(s) merged, all in your team's own repo, nothing sent to codehere.`);
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
console.log(info('Local-first:'), `${view.machines.length} machine(s) merged from ${cfg.dir}, codehere sent nothing to a server.`);
|
|
242
|
-
console.log(info('Tip:'), 'make the team dir a git repo with a remote and codehere will pull/push it for you.');
|
|
243
|
-
}
|
|
244
|
-
// --strict: refuse to exit clean if the record can't be trusted. A tampered
|
|
245
|
-
// signature or a broken audit chain means the team total above is not a
|
|
246
|
-
// trustworthy record — fail closed so CI / a lead's gate catches it.
|
|
247
|
-
if (strict) {
|
|
248
|
-
const bad = [...view.tamperedShares, ...view.brokenChains];
|
|
249
|
-
if (bad.length) {
|
|
250
|
-
newline();
|
|
251
|
-
console.error(error('--strict: record integrity failed.'), `Untrustworthy: ${[...new Set(bad)].join(', ')}.`);
|
|
252
|
-
process.exit(1);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
//# sourceMappingURL=team-command.js.map
|
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI: `codehere thread` · v0.8 S4 · signed-payload share + import
|
|
3
|
-
*
|
|
4
|
-
* Subcommands:
|
|
5
|
-
* export <session-id> [--out <file>] [--json]
|
|
6
|
-
* Sign the session bundle and write it to <file> (or stdout when no --out).
|
|
7
|
-
* import <file> [--trust] [--json]
|
|
8
|
-
* Verify the signature. If the signer's key isn't trusted, refuse unless
|
|
9
|
-
* --trust is set OR the user is on a TTY and confirms. On valid+trusted,
|
|
10
|
-
* merge the session into ~/.codehere/sessions.jsonl so it appears in the
|
|
11
|
-
* dashboard.
|
|
12
|
-
* keys · print the local public key
|
|
13
|
-
* trust <pubkey> [label] · add a public key to the trusted set
|
|
14
|
-
* untrust <pubkey> · remove
|
|
15
|
-
*
|
|
16
|
-
* No network. Both export and import are local file operations.
|
|
17
|
-
*/
|
|
18
|
-
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from 'fs';
|
|
19
|
-
import { homedir, hostname } from 'os';
|
|
20
|
-
import { dirname, join, isAbsolute, resolve } from 'path';
|
|
21
|
-
import { colors, symbols } from '../../../ui.js';
|
|
22
|
-
import { signBundle, verifyBundle, shortFingerprint, collectAuditEntriesForSession, verifyEmbeddedAuditChain, } from '../../../application/use-cases/thread-export.js';
|
|
23
|
-
import { loadOrCreateKeypair, localPublicKeyBase64, trustKey, untrustKey, trustedKeySet, loadTrustedKeys, isKeyTrusted, } from '../../../infrastructure/orchestration/thread-key-store.js';
|
|
24
|
-
import { loadSessionById } from '../../../infrastructure/orchestration/session-persistence.js';
|
|
25
|
-
import { getVersion } from '../../../utils/version.js';
|
|
26
|
-
export function executeThreadCommand(sub, arg, _extra, options = {}) {
|
|
27
|
-
switch (sub) {
|
|
28
|
-
case 'export':
|
|
29
|
-
return exportThread(arg, options);
|
|
30
|
-
case 'import':
|
|
31
|
-
return importThread(arg, options);
|
|
32
|
-
case 'keys':
|
|
33
|
-
return showKeys(options.json ?? false);
|
|
34
|
-
case 'trust':
|
|
35
|
-
return trustCmd(arg, options.label, options.json ?? false);
|
|
36
|
-
case 'untrust':
|
|
37
|
-
return untrustCmd(arg, options.json ?? false);
|
|
38
|
-
default:
|
|
39
|
-
process.stderr.write(colors.error(`${symbols.error} Unknown subcommand: ${sub ?? '(none)'}\n`) +
|
|
40
|
-
colors.dim('Try: codehere thread export <id> | import <file> | keys | trust <key> | untrust <key>\n'));
|
|
41
|
-
return 2;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
45
|
-
/* export */
|
|
46
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
47
|
-
function exportThread(sessionId, options) {
|
|
48
|
-
if (!sessionId) {
|
|
49
|
-
process.stderr.write(colors.error(`${symbols.error} Need a session id\n`));
|
|
50
|
-
return 2;
|
|
51
|
-
}
|
|
52
|
-
const session = loadSessionById(sessionId);
|
|
53
|
-
if (!session) {
|
|
54
|
-
process.stderr.write(colors.error(`${symbols.error} No session found matching "${sessionId}"\n`));
|
|
55
|
-
return 2;
|
|
56
|
-
}
|
|
57
|
-
const keys = loadOrCreateKeypair();
|
|
58
|
-
const body = {
|
|
59
|
-
schema: 1,
|
|
60
|
-
kind: 'thread',
|
|
61
|
-
sessionId: session.sessionId,
|
|
62
|
-
session,
|
|
63
|
-
// Embed the session's tool-call audit so a teammate can inspect what the
|
|
64
|
-
// agent did (signer-attested; see ThreadBundleBody.auditChain trust model).
|
|
65
|
-
auditChain: collectAuditEntriesForSession(session.sessionId),
|
|
66
|
-
exporter: {
|
|
67
|
-
hostname: safeHostname(),
|
|
68
|
-
codehereVersion: getVersion(),
|
|
69
|
-
},
|
|
70
|
-
exportedAt: new Date().toISOString(),
|
|
71
|
-
};
|
|
72
|
-
const bundle = signBundle(body, keys);
|
|
73
|
-
const text = JSON.stringify(bundle, null, 2);
|
|
74
|
-
if (options.out) {
|
|
75
|
-
const outPath = isAbsolute(options.out) ? options.out : resolve(process.cwd(), options.out);
|
|
76
|
-
mkdirSync(dirname(outPath), { recursive: true });
|
|
77
|
-
writeFileSync(outPath, text, 'utf-8');
|
|
78
|
-
if (options.json) {
|
|
79
|
-
process.stdout.write(JSON.stringify({ ok: true, path: outPath, fingerprint: shortFingerprint(bundle.publicKey) }) + '\n');
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
process.stdout.write(colors.green(`${symbols.success} Exported`) +
|
|
83
|
-
colors.dim(` ${outPath}\n`) +
|
|
84
|
-
colors.dim(` signed by ${shortFingerprint(bundle.publicKey)}\n`) +
|
|
85
|
-
colors.dim(` share via Slack/email/PR · recipient runs: `) +
|
|
86
|
-
colors.bold(`codehere thread import ${outPath}\n`));
|
|
87
|
-
}
|
|
88
|
-
return 0;
|
|
89
|
-
}
|
|
90
|
-
// No --out · print to stdout (machine-pipe-friendly)
|
|
91
|
-
process.stdout.write(text + '\n');
|
|
92
|
-
return 0;
|
|
93
|
-
}
|
|
94
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
95
|
-
/* import */
|
|
96
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
97
|
-
function importThread(filePath, options) {
|
|
98
|
-
if (!filePath) {
|
|
99
|
-
process.stderr.write(colors.error(`${symbols.error} Need a file path\n`));
|
|
100
|
-
return 2;
|
|
101
|
-
}
|
|
102
|
-
const fp = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
|
|
103
|
-
if (!existsSync(fp)) {
|
|
104
|
-
process.stderr.write(colors.error(`${symbols.error} Not found: ${fp}\n`));
|
|
105
|
-
return 2;
|
|
106
|
-
}
|
|
107
|
-
let raw;
|
|
108
|
-
try {
|
|
109
|
-
raw = JSON.parse(readFileSync(fp, 'utf-8'));
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
process.stderr.write(colors.error(`${symbols.error} Invalid JSON: ${err instanceof Error ? err.message : String(err)}\n`));
|
|
113
|
-
return 2;
|
|
114
|
-
}
|
|
115
|
-
const verdict = verifyBundle(raw, trustedKeySet());
|
|
116
|
-
if (verdict.status === 'malformed') {
|
|
117
|
-
if (options.json) {
|
|
118
|
-
process.stdout.write(JSON.stringify({ ok: false, error: 'malformed', detail: verdict.reason }) + '\n');
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
process.stderr.write(colors.error(`${symbols.error} Malformed bundle: ${verdict.reason}\n`));
|
|
122
|
-
}
|
|
123
|
-
return 2;
|
|
124
|
-
}
|
|
125
|
-
if (verdict.status === 'tampered') {
|
|
126
|
-
if (options.json) {
|
|
127
|
-
process.stdout.write(JSON.stringify({ ok: false, error: 'tampered', detail: verdict.reason }) + '\n');
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
process.stderr.write(colors.error(`${symbols.error} Signature INVALID · refusing to import\n`) +
|
|
131
|
-
colors.dim(` ${verdict.reason}\n`));
|
|
132
|
-
}
|
|
133
|
-
return 1;
|
|
134
|
-
}
|
|
135
|
-
// status: 'valid' or 'unknown_key'
|
|
136
|
-
const bundle = verdict.status === 'valid' ? verdict.bundle : verdict.bundle;
|
|
137
|
-
const fp_key = shortFingerprint(bundle.publicKey);
|
|
138
|
-
// Re-verify the embedded tool-call audit so the recipient SEES what the agent
|
|
139
|
-
// did — not just the narration. Honest semantics: "verified" is gated on
|
|
140
|
-
// verified>0 (a valid AuditChainCheck is true even for an empty set), and a
|
|
141
|
-
// tampered entry is surfaced loudly. The bundle SIGNATURE already proved the
|
|
142
|
-
// chain is signer-attested + unaltered-in-transit; this confirms each entry's
|
|
143
|
-
// self-hash on top.
|
|
144
|
-
const auditCheck = bundle.body.auditChain
|
|
145
|
-
? verifyEmbeddedAuditChain(bundle.body.auditChain)
|
|
146
|
-
: undefined;
|
|
147
|
-
if (verdict.status === 'unknown_key') {
|
|
148
|
-
if (!options.trust) {
|
|
149
|
-
if (options.json) {
|
|
150
|
-
process.stdout.write(JSON.stringify({
|
|
151
|
-
ok: false,
|
|
152
|
-
error: 'unknown_key',
|
|
153
|
-
publicKey: bundle.publicKey,
|
|
154
|
-
fingerprint: fp_key,
|
|
155
|
-
hint: 'Re-run with --trust to add this key to your trust store and import.',
|
|
156
|
-
}) + '\n');
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
process.stderr.write(colors.warning(`${symbols.warning} Signer's key is not in your trust store\n`) +
|
|
160
|
-
colors.dim(` fingerprint: ${fp_key}\n`) +
|
|
161
|
-
colors.dim(` The signature is cryptographically valid but you've never trusted this key.\n`) +
|
|
162
|
-
colors.dim(` To trust + import: `) +
|
|
163
|
-
colors.bold(`codehere thread import ${fp} --trust\n`) +
|
|
164
|
-
colors.dim(` To inspect first: `) +
|
|
165
|
-
colors.bold(`codehere thread trust ${bundle.publicKey} <label>`) +
|
|
166
|
-
'\n');
|
|
167
|
-
}
|
|
168
|
-
return 2;
|
|
169
|
-
}
|
|
170
|
-
// --trust set · add key to store and continue
|
|
171
|
-
trustKey(bundle.publicKey, options.label);
|
|
172
|
-
}
|
|
173
|
-
// Persist into ~/.codehere/sessions.jsonl as 3 records (spawned, completed, finalReport)
|
|
174
|
-
const ses = bundle.body.session;
|
|
175
|
-
// No-clobber / idempotent import (Phase 2.5 multi-human): re-importing the same
|
|
176
|
-
// session must not duplicate it in a shared room. Append-only never LOSES work,
|
|
177
|
-
// but a second import would double the records (and any count-based view).
|
|
178
|
-
if (loadSessionById(ses.sessionId)) {
|
|
179
|
-
if (options.json) {
|
|
180
|
-
process.stdout.write(JSON.stringify({ ok: true, sessionId: ses.sessionId, alreadyImported: true, fingerprint: fp_key }) + '\n');
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
process.stdout.write(colors.dim(`${symbols.info} session ${ses.sessionId.slice(0, 8)} is already imported · nothing to do\n`));
|
|
184
|
-
}
|
|
185
|
-
return 0;
|
|
186
|
-
}
|
|
187
|
-
const home = process.env.CODEHERE_HOME || homedir();
|
|
188
|
-
const sessionsPath = join(home, '.codehere', 'sessions.jsonl');
|
|
189
|
-
mkdirSync(dirname(sessionsPath), { recursive: true });
|
|
190
|
-
const lines = [];
|
|
191
|
-
lines.push(JSON.stringify({
|
|
192
|
-
t: 'spawned',
|
|
193
|
-
id: ses.sessionId,
|
|
194
|
-
agent: ses.agent,
|
|
195
|
-
prompt: ses.prompt,
|
|
196
|
-
cwd: ses.cwd,
|
|
197
|
-
startedAt: ses.startedAt,
|
|
198
|
-
// S18 · practice classification is a property of the RUN on the exporter's
|
|
199
|
-
// machine, carried by the bundle — never the importing daemon's env.
|
|
200
|
-
// Absent in a legacy bundle = the work was real and audit-attested; an
|
|
201
|
-
// import performed on a mock daemon must never demote a teammate's real
|
|
202
|
-
// work to practice. Stamped explicitly so no spawned-record writer leaves
|
|
203
|
-
// the field env-classified.
|
|
204
|
-
mockRun: ses.mockRun === true,
|
|
205
|
-
// Provenance: attribute this session to the signer it was imported from,
|
|
206
|
-
// so a shared room shows "from teammate <fingerprint>", not as if local.
|
|
207
|
-
importedFrom: fp_key,
|
|
208
|
-
// Persist the audit attestation — the bundle isn't stored, so the room
|
|
209
|
-
// needs this to show the proof badge. attested gated on verified>0 && no tamper.
|
|
210
|
-
...(auditCheck
|
|
211
|
-
? {
|
|
212
|
-
importAudit: {
|
|
213
|
-
total: auditCheck.total,
|
|
214
|
-
verified: auditCheck.verified,
|
|
215
|
-
tampered: auditCheck.tampered.length,
|
|
216
|
-
attested: auditCheck.verified > 0 && auditCheck.tampered.length === 0,
|
|
217
|
-
},
|
|
218
|
-
}
|
|
219
|
-
: {}),
|
|
220
|
-
}));
|
|
221
|
-
lines.push(JSON.stringify({
|
|
222
|
-
t: 'completed',
|
|
223
|
-
id: ses.sessionId,
|
|
224
|
-
status: ses.status,
|
|
225
|
-
durationMs: ses.durationMs,
|
|
226
|
-
completedAt: ses.completedAt,
|
|
227
|
-
steps: ses.steps,
|
|
228
|
-
artifacts: ses.artifacts,
|
|
229
|
-
}));
|
|
230
|
-
if (ses.finalReport) {
|
|
231
|
-
lines.push(JSON.stringify({
|
|
232
|
-
t: 'finalReport',
|
|
233
|
-
id: ses.sessionId,
|
|
234
|
-
summary: ses.finalReport.summary,
|
|
235
|
-
generatedAt: ses.finalReport.generatedAt,
|
|
236
|
-
durationMs: ses.finalReport.durationMs,
|
|
237
|
-
}));
|
|
238
|
-
}
|
|
239
|
-
appendFileSync(sessionsPath, lines.join('\n') + '\n', 'utf-8');
|
|
240
|
-
if (options.json) {
|
|
241
|
-
process.stdout.write(JSON.stringify({
|
|
242
|
-
ok: true,
|
|
243
|
-
sessionId: ses.sessionId,
|
|
244
|
-
trusted: verdict.status === 'valid' ? true : 'just-added',
|
|
245
|
-
fingerprint: fp_key,
|
|
246
|
-
audit: auditCheck
|
|
247
|
-
? {
|
|
248
|
-
total: auditCheck.total,
|
|
249
|
-
verified: auditCheck.verified,
|
|
250
|
-
tampered: auditCheck.tampered.length,
|
|
251
|
-
legacy: auditCheck.legacy,
|
|
252
|
-
// "verified" is true ONLY when entries were actually checked +
|
|
253
|
-
// none tampered — never on a bare/empty chain.
|
|
254
|
-
attested: auditCheck.verified > 0 && auditCheck.tampered.length === 0,
|
|
255
|
-
}
|
|
256
|
-
: null,
|
|
257
|
-
}) + '\n');
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
process.stdout.write(colors.green(`${symbols.success} Imported`) +
|
|
261
|
-
colors.dim(` session ${ses.sessionId.slice(0, 8)} · signed by ${fp_key}\n`));
|
|
262
|
-
process.stdout.write(formatAuditLine(auditCheck));
|
|
263
|
-
process.stdout.write(colors.dim(` view it: `) +
|
|
264
|
-
colors.bold(`codehere start`) +
|
|
265
|
-
colors.dim(` then click the task in the dashboard\n`));
|
|
266
|
-
}
|
|
267
|
-
return 0;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Honest one-line summary of the embedded audit chain for the import surface.
|
|
271
|
-
* The "✓ verified" claim is gated on verified>0 (NOT bare check.valid, which is
|
|
272
|
-
* true for an empty/all-legacy set where nothing was checked); a tampered entry
|
|
273
|
-
* is surfaced as a loud warning. Returns '' when there's no chain to report.
|
|
274
|
-
*/
|
|
275
|
-
function formatAuditLine(check) {
|
|
276
|
-
if (!check || check.total === 0) {
|
|
277
|
-
return colors.dim(` (no tool-call audit was included in this bundle)\n`);
|
|
278
|
-
}
|
|
279
|
-
if (check.tampered.length > 0) {
|
|
280
|
-
return colors.error(` ${symbols.error} audit chain INCONSISTENT · ${check.tampered.length} of ${check.total} entr${check.tampered.length === 1 ? 'y was' : 'ies were'} altered after writing · the signer's tool-call record is not internally consistent\n`);
|
|
281
|
-
}
|
|
282
|
-
if (check.verified > 0) {
|
|
283
|
-
// Honest framing: this is signer ATTESTATION, not independent provenance —
|
|
284
|
-
// the hashes match and the bundle is signed, but trust reduces to the signer's
|
|
285
|
-
// key (a malicious signer could fabricate a consistent chain). Say exactly that
|
|
286
|
-
// rather than a bare "verified" that implies more than self-hash + signature.
|
|
287
|
-
return colors.green(` ${symbols.success} ${check.verified} agent tool call${check.verified === 1 ? '' : 's'} recorded` +
|
|
288
|
-
colors.dim(` · hashes intact, as attested by this signer (your trust = the key above)\n`));
|
|
289
|
-
}
|
|
290
|
-
// total>0 but nothing verifiable (all legacy/no chain fields).
|
|
291
|
-
return colors.dim(` ${check.legacy} audit entr${check.legacy === 1 ? 'y' : 'ies'} present but unverifiable (no chain fields)\n`);
|
|
292
|
-
}
|
|
293
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
294
|
-
/* keys / trust / untrust */
|
|
295
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
296
|
-
function showKeys(json) {
|
|
297
|
-
loadOrCreateKeypair();
|
|
298
|
-
const localB64 = localPublicKeyBase64() ?? '';
|
|
299
|
-
const trusted = loadTrustedKeys();
|
|
300
|
-
if (json) {
|
|
301
|
-
process.stdout.write(JSON.stringify({
|
|
302
|
-
local: { publicKey: localB64, fingerprint: shortFingerprint(localB64) },
|
|
303
|
-
trusted: Object.entries(trusted.keys).map(([k, v]) => ({
|
|
304
|
-
publicKey: k,
|
|
305
|
-
fingerprint: shortFingerprint(k),
|
|
306
|
-
label: v.label,
|
|
307
|
-
addedAt: v.addedAt,
|
|
308
|
-
})),
|
|
309
|
-
}, null, 2) + '\n');
|
|
310
|
-
return 0;
|
|
311
|
-
}
|
|
312
|
-
process.stdout.write('\n' + colors.bold('This machine') + '\n');
|
|
313
|
-
process.stdout.write(' ' + colors.cyan(shortFingerprint(localB64)) + colors.dim(' signs your exports\n\n'));
|
|
314
|
-
process.stdout.write(colors.bold('Trusted keys') + colors.dim(' (imports from these accepted silently)\n'));
|
|
315
|
-
const entries = Object.entries(trusted.keys);
|
|
316
|
-
if (entries.length === 0) {
|
|
317
|
-
process.stdout.write(' ' + colors.dim('(empty)\n'));
|
|
318
|
-
}
|
|
319
|
-
else {
|
|
320
|
-
for (const [k, v] of entries) {
|
|
321
|
-
const label = v.label || '(no label)';
|
|
322
|
-
process.stdout.write(' ' + colors.cyan(shortFingerprint(k)) + ' ' + colors.dim(label) + '\n');
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
process.stdout.write('\n' + colors.dim('Add a key: ') + colors.bold('codehere thread trust <pubkey> <label>') + '\n');
|
|
326
|
-
process.stdout.write(colors.dim('Remove a key: ') + colors.bold('codehere thread untrust <pubkey>') + '\n\n');
|
|
327
|
-
return 0;
|
|
328
|
-
}
|
|
329
|
-
function trustCmd(pubkey, label, json) {
|
|
330
|
-
if (!pubkey) {
|
|
331
|
-
process.stderr.write(colors.error(`${symbols.error} Need a public key (base64)\n`));
|
|
332
|
-
return 2;
|
|
333
|
-
}
|
|
334
|
-
trustKey(pubkey, label);
|
|
335
|
-
if (json) {
|
|
336
|
-
process.stdout.write(JSON.stringify({ ok: true, fingerprint: shortFingerprint(pubkey) }) + '\n');
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
process.stdout.write(colors.green(`${symbols.success} trusted ${shortFingerprint(pubkey)}\n`));
|
|
340
|
-
}
|
|
341
|
-
return 0;
|
|
342
|
-
}
|
|
343
|
-
function untrustCmd(pubkey, json) {
|
|
344
|
-
if (!pubkey) {
|
|
345
|
-
process.stderr.write(colors.error(`${symbols.error} Need a public key (base64)\n`));
|
|
346
|
-
return 2;
|
|
347
|
-
}
|
|
348
|
-
const ok = untrustKey(pubkey);
|
|
349
|
-
if (json) {
|
|
350
|
-
process.stdout.write(JSON.stringify({ ok }) + '\n');
|
|
351
|
-
}
|
|
352
|
-
else if (ok) {
|
|
353
|
-
process.stdout.write(colors.green(`${symbols.success} untrusted ${shortFingerprint(pubkey)}\n`));
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
process.stdout.write(colors.gray(`${symbols.dot} key not in trust store (or it's this machine's own key · can't remove)\n`));
|
|
357
|
-
}
|
|
358
|
-
return 0;
|
|
359
|
-
}
|
|
360
|
-
function safeHostname() {
|
|
361
|
-
try {
|
|
362
|
-
return hostname();
|
|
363
|
-
}
|
|
364
|
-
catch {
|
|
365
|
-
return undefined;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
// Public alias for the connection-status check used in the daemon HTTP routes.
|
|
369
|
-
export { isKeyTrusted };
|
|
370
|
-
//# sourceMappingURL=thread-command.js.map
|