@dashclaw/cli 0.7.6 → 0.8.1

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/bin/dashclaw.js CHANGED
@@ -12,22 +12,11 @@ import {
12
12
  } from '../lib/render.js';
13
13
  import { runDoctor as runDoctorCommand } from '../lib/doctor.js';
14
14
  import { resolveConfig, clearConfigFile, configPath, ask, askSecret } from '../lib/config.js';
15
- import { runIngest, defaultClaudeProjectsDir } from '../lib/code/ingest.js';
16
- import { runMemo } from '../lib/code/memo.js';
17
- import { runApply } from '../lib/code/apply.js';
18
- import {
19
- runCodexIngest,
20
- defaultCodexSessionsDir,
21
- defaultCodexOutDir,
22
- } from '../lib/code/ingest-codex.js';
23
15
  import { installCodex, codexConfigPath, codexHooksDir } from '../lib/codex/install.js';
24
16
  import { installClaude } from '../lib/claude/install.js';
25
17
  import { upCommand, runDown, resolveBaseDir } from '../lib/up/index.js';
26
- import { runCost } from '../lib/cost.js';
27
18
  import { runCodexNotify } from '../lib/codex/notify.js';
28
19
  import { apiRequest } from '../lib/api.js';
29
- import { fetchPosture, fetchFindings, fetchNext, resolveFinding } from '../lib/posture.js';
30
- import { fetchAgentEnv, splitEnvArgv, formatEnvNames, runWithEnv } from '../lib/env.js';
31
20
 
32
21
  process.on('unhandledRejection', (reason) => {
33
22
  console.error('Unhandled Rejection:', reason);
@@ -61,10 +50,6 @@ function getFlag(name) {
61
50
  return undefined;
62
51
  }
63
52
 
64
- function incrementCount(counts, key) {
65
- counts[key] = (counts[key] || 0) + 1;
66
- }
67
-
68
53
  function runSubcommand(table, sub, errorMessage) {
69
54
  const handler = table[sub];
70
55
  if (handler) return handler();
@@ -98,19 +83,6 @@ ${bold('Usage:')}
98
83
  --no-fix Accepted no-op alias (report-only is the default)
99
84
  --category <list> Filter remote checks (e.g., database,config)
100
85
  --repo <path> Treat <path> as the DashClaw checkout for repo checks
101
- dashclaw code ingest [--dry-run] Backfill Claude Code transcripts from ~/.claude/projects
102
- --projects-dir <path> Override the default scan directory
103
- dashclaw code ingest-codex [--dry-run] Backfill Codex transcripts from ~/.codex/sessions
104
- --sessions-dir <path> Override the default scan directory
105
- --out <dir> Local output dir (default ~/.dashclaw/codex-sessions)
106
- --endpoint <url> POST to <url> instead of writing local (advanced)
107
- dashclaw code memo --project=<slug> Print the latest weekly memo for a project
108
- --save Also write to ./memos/<weekTag>-<slug>.md
109
- dashclaw code apply <manifestId> Apply an Optimal Files manifest (Phase 6+ feature)
110
- --dest=<dir> Target project directory (required)
111
- --yes Overwrite existing files when manifest says so
112
- --allow-redactions Write files that contain redacted secret patterns
113
- --overwrite Clobber existing .NEW side-by-side files
114
86
  dashclaw install claude [--trial] Provision DashClaw governance into Claude Code
115
87
  --endpoint <url> Your DashClaw instance URL (or DASHCLAW_BASE_URL;
116
88
  --trial defaults to https://hosted.dashclaw.io)
@@ -122,34 +94,6 @@ ${bold('Usage:')}
122
94
  --include-notify Also wire Codex's notify config to dashclaw codex notify
123
95
  dashclaw codex notify '<json>' Record a Codex turn-complete event
124
96
  (called by Codex's notify config; always exits 0)
125
- dashclaw prompts list [--category C] List prompt templates
126
- dashclaw prompts get <id> Show a template
127
- dashclaw prompts versions <id> List a template's versions
128
- dashclaw prompts render <templateId> Render a prompt
129
- --version-id <vid> Render a specific version instead of the active one
130
- --var <key=value> Set a variable (repeatable)
131
- --record Record the render as a prompt run
132
- dashclaw prompts create --name N Create a template (admin)
133
- --description <D> --category <C>
134
- dashclaw prompts add-version <id> --content C Add a version (admin)
135
- --changelog <L> --model-hint <M>
136
- dashclaw prompts activate <id> <vid> Activate a version (admin)
137
- dashclaw prompts stats [--template-id X] Prompt run analytics
138
- dashclaw inbox list [--unread] [--limit N] List inbox messages
139
- dashclaw inbox read <id> [<id> ...] Mark messages read
140
- dashclaw inbox archive <id> [<id> ...] Archive messages
141
- dashclaw behavior status Behavior Learning sample status (local recorder)
142
- dashclaw behavior suggestions Evidence-backed policy suggestions per agent
143
- --agent-id <id> Filter to one agent
144
- dashclaw env [--agent <id>] -- <cmd...> Run <cmd> with managed secrets injected (memory-only)
145
- Without -- <cmd>: list secret NAMES + count (never values)
146
- dashclaw cost Spend readback from /api/finops/spend
147
- --lens fleet|claude-code Which rollup (default: claude-code)
148
- --period 7d|30d|90d Window (default: 7d)
149
- dashclaw posture Governance posture score + remediation queue
150
- dashclaw posture resolve <key> Draft a fix (inactive) | --snooze | --accept-risk
151
- --note "..." Attach a note to the resolution
152
- dashclaw next The single top open governance gap + its fix
153
97
  dashclaw logout Remove saved config (~/.dashclaw/config.json)
154
98
  dashclaw version Print the CLI version
155
99
  dashclaw help Show this help
@@ -507,33 +451,6 @@ async function cmdHalt() {
507
451
  }
508
452
  }
509
453
 
510
- async function cmdCost() {
511
- const lens = getFlag('--lens') || 'claude-code';
512
- const period = getFlag('--period') || '7d';
513
-
514
- if (!baseUrl || !apiKey) {
515
- console.error('Not configured. Run `dashclaw install claude` to set up,');
516
- console.error('or set DASHCLAW_BASE_URL and DASHCLAW_API_KEY.');
517
- process.exit(1);
518
- }
519
-
520
- try {
521
- console.log(await runCost({ baseUrl, apiKey }, { lens, period }));
522
- } catch (err) {
523
- if (err.usage) {
524
- console.error(err.message);
525
- } else if (err.status === 401 || err.status === 403) {
526
- console.error('API key was rejected (401/403). Re-run `dashclaw install claude` or check DASHCLAW_API_KEY.');
527
- } else {
528
- console.error(`Could not fetch spend from ${baseUrl}: ${err.message}`);
529
- console.error('Check that the instance is running and reachable.');
530
- }
531
- // exitCode (not process.exit) so node drains the failed fetch socket —
532
- // a hard exit here can trip a libuv teardown assert on Windows.
533
- process.exitCode = 1;
534
- }
535
- }
536
-
537
454
  async function cmdInstallClaude() {
538
455
  try {
539
456
  const result = await installClaude({
@@ -656,693 +573,16 @@ async function cmdCodex() {
656
573
  'Try: dashclaw codex notify \'<json>\' (called by Codex notify config)');
657
574
  }
658
575
 
659
- // -- code subcommand group ---------------------------------------------------
660
-
661
- async function cmdCodeIngestCodex() {
662
- const dryRun = args.includes('--dry-run');
663
- const sessionsDir = getFlag('--sessions-dir') || defaultCodexSessionsDir();
664
- const outDir = getFlag('--out') || defaultCodexOutDir();
665
- const endpoint = getFlag('--endpoint') || null;
666
- console.log(`Scanning ${sessionsDir} ...`);
667
- const results = await runCodexIngest({
668
- sessionsDir,
669
- outDir,
670
- endpoint,
671
- apiKey,
672
- dryRun,
673
- logger: console,
674
- });
675
- if (!results.length) {
676
- console.log('No sessions to ingest.');
677
- return;
678
- }
679
- const counts = tallyCodexIngestResults(results);
680
- console.log();
681
- console.log(`Done. Written: ${counts.written_local} Ingested: ${counts.ingested} Dry-run: ${counts.dry_run} Skipped: ${counts.skipped} Errors: ${counts.error}`);
682
- if (!endpoint && counts.written_local > 0) {
683
- console.log(dim(` Local sessions saved under ${outDir}.`));
684
- console.log(dim(` Server-side codex ingest will be wired in a follow-up phase.`));
685
- }
686
- if (counts.error > 0) process.exit(2);
687
- }
688
-
689
- function tallyCodexIngestResults(results) {
690
- const counts = { written_local: 0, ingested: 0, dry_run: 0, skipped: 0, error: 0 };
691
- for (const r of results) {
692
- incrementCount(counts, r.status);
693
- if (r.status === 'error') console.error(formatCodexIngestError(r));
694
- }
695
- return counts;
696
- }
697
-
698
- function formatCodexIngestError(result) {
699
- return ` ${red('error')} ${result.file}: ${result.reason}${result.detail ? ' — ' + result.detail : ''}`;
700
- }
701
-
702
- async function cmdCodeIngest() {
703
- const dryRun = args.includes('--dry-run');
704
- const projectsDir = getFlag('--projects-dir') || defaultClaudeProjectsDir();
705
- console.log(`Scanning ${projectsDir} ...`);
706
- const results = await runIngest({
707
- baseUrl,
708
- apiKey,
709
- projectsDir,
710
- dryRun,
711
- });
712
- if (!results.length) return;
713
- const counts = { ingested: 0, skipped: 0, error: 0 };
714
- for (const r of results) {
715
- if (r.status === 'skipped_unchanged' || r.status === 'dry_run') {
716
- incrementCount(counts, 'skipped');
717
- } else {
718
- incrementCount(counts, r.status);
719
- }
720
- }
721
- console.log();
722
- console.log(`Done. Ingested: ${counts.ingested} Skipped: ${counts.skipped} Errors: ${counts.error}`);
723
- if (counts.error > 0) process.exit(2);
724
- }
725
-
726
- async function cmdCodeMemo() {
727
- const project = getFlag('--project');
728
- const save = args.includes('--save');
729
- if (!project) {
730
- console.error('Error: --project=<slug-or-id> is required.');
731
- process.exit(1);
732
- }
733
- await runMemo({ baseUrl, apiKey, project, save });
734
- }
735
-
736
- async function cmdCodeApply() {
737
- const manifestId = args[2];
738
- const dest = getFlag('--dest');
739
- const yes = args.includes('--yes');
740
- const allowRedactions = args.includes('--allow-redactions');
741
- const overwrite = args.includes('--overwrite');
742
- if (!manifestId) {
743
- console.error('Error: usage — dashclaw code apply <manifestId> --dest=<dir> [--yes] [--allow-redactions] [--overwrite]');
744
- process.exit(1);
745
- }
746
- if (!dest) {
747
- console.error('Error: --dest=<dir> is required.');
748
- process.exit(1);
749
- }
750
- try {
751
- const results = await runApply({
752
- baseUrl,
753
- apiKey,
754
- manifestId,
755
- dest,
756
- yes,
757
- allowRedactions,
758
- allowOverwriteSideBySide: overwrite,
759
- });
760
- const summary = results.reduce((acc, r) => {
761
- acc[r.status] = (acc[r.status] || 0) + 1;
762
- return acc;
763
- }, {});
764
- console.log();
765
- console.log('Apply summary:', JSON.stringify(summary));
766
- } catch (err) {
767
- console.error('Error: ' + err.message);
768
- process.exit(1);
769
- }
770
- }
771
-
772
- async function cmdCode() {
773
- return runSubcommand({
774
- ingest: cmdCodeIngest,
775
- 'ingest-codex': cmdCodeIngestCodex,
776
- memo: cmdCodeMemo,
777
- apply: cmdCodeApply,
778
- }, args[1], (sub) => `Unknown subcommand: dashclaw code ${sub || '(missing)'}\n` +
779
- 'Try: dashclaw code ingest [--dry-run]\n' +
780
- ' dashclaw code ingest-codex [--dry-run] [--out <dir>] [--endpoint <url>]\n' +
781
- ' dashclaw code memo --project=<slug> [--save]\n' +
782
- ' dashclaw code apply <manifestId> --dest=<dir> [--yes]');
783
- }
784
-
785
- // -- prompts subcommand group ------------------------------------------------
786
- //
787
- // Direct-API calls (apiRequest) rather than SDK methods: the CLI imports the
788
- // PUBLISHED `dashclaw` package, which may lag this repo and lack newly-added
789
- // prompt methods. fetch + x-api-key against the resolved baseUrl/apiKey is the
790
- // durable path.
791
-
792
- function promptsClient() {
793
- return { baseUrl, apiKey };
794
- }
795
-
796
- // Collect repeated `--var key=value` flags into a { key: value } object.
797
- function parseVars() {
798
- const vars = {};
799
- for (let i = 0; i < args.length; i++) {
800
- if (args[i] === '--var' && i + 1 < args.length) {
801
- const pair = args[i + 1];
802
- const eq = pair.indexOf('=');
803
- if (eq === -1) {
804
- console.error(`Error: --var expects key=value, got "${pair}"`);
805
- process.exit(1);
806
- }
807
- vars[pair.slice(0, eq)] = pair.slice(eq + 1);
808
- }
809
- }
810
- return vars;
811
- }
812
-
813
- async function cmdPromptsList() {
814
- const category = getFlag('--category');
815
- try {
816
- const data = await apiRequest(promptsClient(), 'GET', '/api/prompts/templates', {
817
- query: { category },
818
- });
819
- const templates = data.templates || [];
820
- if (templates.length === 0) {
821
- console.log(dim(' No templates.'));
822
- return;
823
- }
824
- for (const t of templates) {
825
- console.log(
826
- ` ${bold(t.id)} ${t.name || '-'} ${dim('[' + (t.category || 'uncategorized') + ']')}` +
827
- ` active v${t.active_version ?? '-'} ${dim('(' + (t.version_count ?? 0) + ' versions)')}`,
828
- );
829
- }
830
- } catch (err) {
831
- console.error(`Error: ${err.message}`);
832
- process.exit(1);
833
- }
834
- }
835
-
836
- async function cmdPromptsGet() {
837
- const id = args[2];
838
- if (!id) {
839
- console.error('Error: Missing template ID. Usage: dashclaw prompts get <id>');
840
- process.exit(1);
841
- }
842
- try {
843
- const data = await apiRequest(promptsClient(), 'GET', `/api/prompts/templates/${encodeURIComponent(id)}`);
844
- console.log(JSON.stringify(data, null, 2));
845
- } catch (err) {
846
- console.error(`Error: ${err.message}`);
847
- process.exit(1);
848
- }
849
- }
850
-
851
- async function cmdPromptsVersions() {
852
- const id = args[2];
853
- if (!id) {
854
- console.error('Error: Missing template ID. Usage: dashclaw prompts versions <id>');
855
- process.exit(1);
856
- }
857
- try {
858
- const data = await apiRequest(
859
- promptsClient(), 'GET', `/api/prompts/templates/${encodeURIComponent(id)}/versions`,
860
- );
861
- const versions = data.versions || [];
862
- if (versions.length === 0) {
863
- console.log(dim(' No versions.'));
864
- return;
865
- }
866
- for (const v of versions) {
867
- const active = v.is_active ? green(' (active)') : '';
868
- console.log(` v${v.version} ${bold(v.id)}${active} ${dim(v.changelog || '')}`);
869
- }
870
- } catch (err) {
871
- console.error(`Error: ${err.message}`);
872
- process.exit(1);
873
- }
874
- }
875
-
876
- async function cmdPromptsRender() {
877
- const versionId = getFlag('--version-id');
878
- const templateId = args[2] && !args[2].startsWith('--') ? args[2] : undefined;
879
- if (!templateId && !versionId) {
880
- console.error('Error: usage — dashclaw prompts render <templateId> [--var k=v ...] [--record]');
881
- console.error(' or: dashclaw prompts render --version-id <vid> [--var k=v ...]');
882
- process.exit(1);
883
- }
884
- const variables = parseVars();
885
- const record = args.includes('--record');
886
- try {
887
- const data = await apiRequest(promptsClient(), 'POST', '/api/prompts/render', {
888
- body: {
889
- template_id: templateId,
890
- version_id: versionId,
891
- variables,
892
- agent_id: agentId,
893
- record,
894
- },
895
- });
896
- console.log(data.rendered ?? '');
897
- if (Array.isArray(data.parameters) && data.parameters.length > 0) {
898
- console.log();
899
- console.log(dim(` parameters: ${data.parameters.join(', ')}`));
900
- }
901
- } catch (err) {
902
- console.error(`Error: ${err.message}`);
903
- process.exit(1);
904
- }
905
- }
906
-
907
- async function cmdPromptsCreate() {
908
- const name = getFlag('--name');
909
- if (!name) {
910
- console.error('Error: --name is required. Usage: dashclaw prompts create --name N [--description D] [--category C]');
911
- process.exit(1);
912
- }
913
- const description = getFlag('--description');
914
- const category = getFlag('--category');
915
- try {
916
- const data = await apiRequest(promptsClient(), 'POST', '/api/prompts/templates', {
917
- body: { name, description, category },
918
- });
919
- console.log(` ${green('Created')} ${bold(data.id)} ${data.name}`);
920
- } catch (err) {
921
- console.error(`Error: ${err.message}`);
922
- process.exit(1);
923
- }
924
- }
925
-
926
- async function cmdPromptsAddVersion() {
927
- const templateId = args[2];
928
- if (!templateId || templateId.startsWith('--')) {
929
- console.error('Error: usage — dashclaw prompts add-version <templateId> --content C [--changelog L] [--model-hint M]');
930
- process.exit(1);
931
- }
932
- const content = getFlag('--content');
933
- if (!content) {
934
- console.error('Error: --content is required.');
935
- process.exit(1);
936
- }
937
- const changelog = getFlag('--changelog');
938
- const modelHint = getFlag('--model-hint');
939
- try {
940
- const data = await apiRequest(
941
- promptsClient(), 'POST', `/api/prompts/templates/${encodeURIComponent(templateId)}/versions`,
942
- { body: { content, changelog, model_hint: modelHint } },
943
- );
944
- console.log(` ${green('Added')} v${data.version} ${bold(data.id)}`);
945
- } catch (err) {
946
- console.error(`Error: ${err.message}`);
947
- process.exit(1);
948
- }
949
- }
950
-
951
- async function cmdPromptsActivate() {
952
- const templateId = args[2];
953
- const versionId = args[3];
954
- if (!templateId || !versionId) {
955
- console.error('Error: usage — dashclaw prompts activate <templateId> <versionId>');
956
- process.exit(1);
957
- }
958
- try {
959
- await apiRequest(
960
- promptsClient(), 'POST',
961
- `/api/prompts/templates/${encodeURIComponent(templateId)}/versions/${encodeURIComponent(versionId)}`,
962
- );
963
- console.log(` ${green('Activated')} version ${bold(versionId)}`);
964
- } catch (err) {
965
- console.error(`Error: ${err.message}`);
966
- process.exit(1);
967
- }
968
- }
969
-
970
- async function cmdPromptsStats() {
971
- const templateId = getFlag('--template-id');
972
- try {
973
- const data = await apiRequest(promptsClient(), 'GET', '/api/prompts/stats', {
974
- query: { template_id: templateId },
975
- });
976
- console.log(JSON.stringify(data, null, 2));
977
- } catch (err) {
978
- console.error(`Error: ${err.message}`);
979
- process.exit(1);
980
- }
981
- }
982
-
983
- async function cmdPrompts() {
984
- const sub = args[1];
985
- switch (sub) {
986
- case 'list':
987
- return cmdPromptsList();
988
- case 'get':
989
- return cmdPromptsGet();
990
- case 'versions':
991
- return cmdPromptsVersions();
992
- case 'render':
993
- return cmdPromptsRender();
994
- case 'create':
995
- return cmdPromptsCreate();
996
- case 'add-version':
997
- return cmdPromptsAddVersion();
998
- case 'activate':
999
- return cmdPromptsActivate();
1000
- case 'stats':
1001
- return cmdPromptsStats();
1002
- default:
1003
- console.error(`Unknown subcommand: dashclaw prompts ${sub || '(missing)'}\n` +
1004
- 'Try: dashclaw prompts list [--category C]\n' +
1005
- ' dashclaw prompts get <id>\n' +
1006
- ' dashclaw prompts versions <id>\n' +
1007
- ' dashclaw prompts render <templateId> [--var k=v ...] [--record]\n' +
1008
- ' dashclaw prompts create --name N [--description D] [--category C]\n' +
1009
- ' dashclaw prompts add-version <templateId> --content C [--changelog L] [--model-hint M]\n' +
1010
- ' dashclaw prompts activate <templateId> <versionId>\n' +
1011
- ' dashclaw prompts stats [--template-id X]');
1012
- process.exit(1);
1013
- }
1014
- }
1015
-
1016
- // -- inbox subcommand group --------------------------------------------------
1017
- //
1018
- // Direct-API calls against /api/messages (durable path, see prompts group).
1019
- // Uses the resolved `agentId` for direction=inbox filtering and PATCH attribution.
1020
-
1021
- function inboxClient() {
1022
- return { baseUrl, apiKey };
1023
- }
1024
-
1025
- async function cmdInboxList() {
1026
- try {
1027
- const data = await apiRequest(inboxClient(), 'GET', '/api/messages', { query: inboxListQuery() });
1028
- renderInboxList(data);
1029
- } catch (err) {
1030
- console.error(`Error: ${err.message}`);
1031
- process.exit(1);
1032
- }
1033
- }
1034
-
1035
- function inboxListQuery() {
1036
- return {
1037
- agent_id: agentId,
1038
- direction: 'inbox',
1039
- unread: args.includes('--unread') ? 'true' : undefined,
1040
- limit: getFlag('--limit'),
1041
- };
1042
- }
1043
-
1044
- function renderInboxList(data) {
1045
- const messages = data.messages || [];
1046
- if (messages.length === 0) {
1047
- console.log(dim(' No messages.'));
1048
- } else {
1049
- messages.forEach(renderInboxMessage);
1050
- }
1051
- console.log();
1052
- console.log(dim(` unread: ${data.unread_count ?? 0}`));
1053
- }
1054
-
1055
- function renderInboxMessage(message) {
1056
- const readMark = message.is_read ? dim('read') : green('unread');
1057
- console.log(
1058
- ` ${bold(message.id)} ${dim('from')} ${message.from_agent_id || '-'} ${message.subject || dim('(no subject)')} [${readMark}]`,
1059
- );
1060
- }
1061
-
1062
- async function cmdInboxUpdate(action) {
1063
- const ids = args.slice(2).filter((a) => !a.startsWith('--'));
1064
- if (ids.length === 0) {
1065
- console.error(`Error: usage — dashclaw inbox ${action} <id> [<id> ...]`);
1066
- process.exit(1);
1067
- }
1068
- try {
1069
- const data = await apiRequest(inboxClient(), 'PATCH', '/api/messages', {
1070
- body: { message_ids: ids, action, agent_id: agentId },
1071
- });
1072
- console.log(` ${green('Updated')} ${data.updated ?? 0} message(s)`);
1073
- } catch (err) {
1074
- console.error(`Error: ${err.message}`);
1075
- process.exit(1);
1076
- }
1077
- }
1078
-
1079
- async function cmdInbox() {
1080
- return runSubcommand({
1081
- list: cmdInboxList,
1082
- read: () => cmdInboxUpdate('read'),
1083
- archive: () => cmdInboxUpdate('archive'),
1084
- }, args[1], (sub) => `Unknown subcommand: dashclaw inbox ${sub || '(missing)'}\n` +
1085
- 'Try: dashclaw inbox list [--unread] [--limit N]\n' +
1086
- ' dashclaw inbox read <id> [<id> ...]\n' +
1087
- ' dashclaw inbox archive <id> [<id> ...]');
1088
- }
1089
-
1090
- // -- behavior subcommand group -----------------------------------------------
1091
- //
1092
- // Behavior Learning / Policy Coach. Read-only inspection of the locally-recorded
1093
- // behavior samples and the evidence-backed policy suggestions derived from them.
1094
- // Direct-API calls (durable path, see prompts/inbox groups). Adopt/dismiss are
1095
- // intentionally UI-only in V1 — they require simulation review.
1096
-
1097
- function behaviorClient() {
1098
- return { baseUrl, apiKey };
1099
- }
1100
-
1101
- async function cmdBehaviorStatus() {
1102
- try {
1103
- const data = await apiRequest(behaviorClient(), 'GET', '/api/behavior/samples');
1104
- renderBehaviorStatus(data);
1105
- } catch (err) {
1106
- console.error(`Error: ${err.message}`);
1107
- process.exit(1);
1108
- }
1109
- }
1110
-
1111
- function behaviorReadyLabel(data) {
1112
- return data.ready ? green('yes') : dim('no — need ' + (data.min_samples ?? 8) + '+ samples for an agent');
1113
- }
1114
-
1115
- function renderBehaviorStatus(data) {
1116
- console.log(` Recorder: ${data.recorder_enabled ? green('on') : dim('off')}`);
1117
- console.log(` Directory: ${dim(data.dir || '-')}`);
1118
- console.log(` Samples: ${bold(String(data.sample_count ?? 0))} ${dim('across')} ${data.agent_count ?? 0} ${dim('agent(s)')}`);
1119
- console.log(` Window: ${dim((data.oldest_ts || '-') + ' → ' + (data.newest_ts || '-'))}`);
1120
- console.log(` Ready: ${behaviorReadyLabel(data)}`);
1121
- for (const a of data.agents || []) {
1122
- console.log(` ${a.agent_id} ${dim(a.count + ' samples')}`);
1123
- }
1124
- }
1125
-
1126
- async function cmdBehaviorSuggestions() {
1127
- const agent = getFlag('--agent-id') || agentId;
1128
- try {
1129
- const data = await apiRequest(behaviorClient(), 'GET', '/api/behavior/suggestions', {
1130
- query: { agent_id: agent },
1131
- });
1132
- const suggestions = data.suggestions || [];
1133
- if (suggestions.length === 0) {
1134
- console.log(dim(` No suggestions (${data.sample_count ?? 0} samples analyzed).`));
1135
- return;
1136
- }
1137
- for (const s of suggestions) {
1138
- const kind = s.enforceable ? green('draft') : dim('advisory');
1139
- console.log(` ${bold(s.type)} ${dim(s.agent_id)} ${s.confidence}% [${kind}] ${dim(s.severity)}`);
1140
- console.log(` ${s.expected_effect}`);
1141
- console.log(dim(` evidence: ${s.matching_sample_size}/${s.sample_size} · id ${s.id}`));
1142
- }
1143
- console.log();
1144
- console.log(dim(' Review + simulate + adopt from the Policy Coach UI (/policy-coach).'));
1145
- } catch (err) {
1146
- console.error(`Error: ${err.message}`);
1147
- process.exit(1);
1148
- }
1149
- }
1150
-
1151
- async function cmdBehavior() {
1152
- return runSubcommand({
1153
- status: cmdBehaviorStatus,
1154
- suggestions: cmdBehaviorSuggestions,
1155
- }, args[1], (sub) => `Unknown subcommand: dashclaw behavior ${sub || '(missing)'}\n` +
1156
- 'Try: dashclaw behavior status\n' +
1157
- ' dashclaw behavior suggestions [--agent-id X]');
1158
- }
1159
-
1160
- // -- posture subcommand group ------------------------------------------------
1161
- //
1162
- // Direct-API (apiRequest via cli/lib/posture.js) — the governance posture score
1163
- // + the prioritized remediation queue. `posture resolve` is DRAFT-ONLY: it can
1164
- // create an inactive policy draft / snooze / accept risk, never activate
1165
- // enforcement (a human does that at /policies).
1166
-
1167
- function postureClient() {
1168
- return { baseUrl, apiKey };
1169
- }
1170
-
1171
- const POSTURE_DIM_LABEL = {
1172
- identity: 'Identity', enforcement: 'Enforcement', spend: 'Spend',
1173
- auditability: 'Audit', approval: 'Approval', data_protection: 'Data',
1174
- };
1175
-
1176
- function printFinding(f, indent = ' ') {
1177
- console.log(`${indent}${bold('+' + (f.scoreDelta ?? 0))} ${dim('[' + f.severity + ']')} ${f.title}`);
1178
- console.log(dim(`${indent} ${f.key}`));
1179
- }
1180
-
1181
- function postureStatusLabel(status) {
1182
- if (status === 'healthy') return green(status);
1183
- if (status === 'at_risk') return red(status);
1184
- return status;
1185
- }
1186
-
1187
- function renderPostureHeader(data) {
1188
- console.log();
1189
- console.log(` ${bold('Governance posture')} ${bold(String(data.score))}${dim('/100')} ${postureStatusLabel(data.status)}` +
1190
- `${data.cappedBy ? ' ' + red('[capped: incident]') : ''}`);
1191
- console.log(dim(` ${data.summary?.openFindings ?? 0} open · +${Math.round(data.summary?.pointsRecoverable ?? 0)} points recoverable`));
1192
- console.log();
1193
- }
1194
-
1195
- function renderPostureDimensions(dimensions) {
1196
- for (const d of dimensions || []) {
1197
- const label = POSTURE_DIM_LABEL[d.dimension] || d.dimension;
1198
- const mark = d.score < 70 ? red('!') : ' ';
1199
- console.log(` ${mark} ${label.padEnd(12)} ${String(d.score).padStart(3)}`);
1200
- }
1201
- console.log();
1202
- }
1203
-
1204
- function renderPostureQueue(findings) {
1205
- console.log(dim(` Next — ${findings.length} open`));
1206
- if (findings.length === 0) {
1207
- console.log(green(' Queue is clear — no open coverage gaps.'));
1208
- return;
1209
- }
1210
- for (const f of findings.slice(0, 5)) printFinding(f);
1211
- if (findings.length > 5) console.log(dim(` … ${findings.length - 5} more`));
1212
- }
1213
-
1214
- async function cmdPostureShow() {
1215
- try {
1216
- const client = postureClient();
1217
- const [data, queue] = await Promise.all([fetchPosture(client), fetchFindings(client)]);
1218
- const findings = queue.findings || [];
1219
- renderPostureHeader(data);
1220
- renderPostureDimensions(data.dimensions);
1221
- renderPostureQueue(findings);
1222
- console.log();
1223
- } catch (err) {
1224
- console.error(`Error: ${err.message}`);
1225
- process.exit(1);
1226
- }
1227
- }
1228
-
1229
- async function cmdPostureResolve() {
1230
- const key = args[2];
1231
- if (!key || key.startsWith('-')) {
1232
- console.error('Error: usage — dashclaw posture resolve <key> [--snooze | --accept-risk] [--note "..."]');
1233
- process.exit(1);
1234
- }
1235
- const action = args.includes('--snooze') ? 'snooze'
1236
- : args.includes('--accept-risk') ? 'accept_risk' : 'create_draft';
1237
- try {
1238
- await resolveFinding(postureClient(), key, action, getFlag('--note'));
1239
- if (action === 'create_draft') {
1240
- console.log(green(' Draft created (inactive).') +
1241
- dim(' Activate it at /policies and rescan — drafting does not change the score.'));
1242
- } else {
1243
- console.log(green(` Finding ${action === 'snooze' ? 'snoozed' : 'risk-accepted'}.`));
1244
- }
1245
- } catch (err) {
1246
- console.error(`Error: ${err.message}`);
1247
- process.exit(1);
1248
- }
1249
- }
1250
-
1251
- async function cmdPosture() {
1252
- const sub = args[1];
1253
- if (sub === undefined) return cmdPostureShow();
1254
- if (sub === 'resolve') return cmdPostureResolve();
1255
- console.error(`Unknown subcommand: dashclaw posture ${sub}\n` +
1256
- 'Try: dashclaw posture\n dashclaw posture resolve <key>');
1257
- process.exit(1);
1258
- }
1259
-
1260
- async function cmdNext() {
1261
- try {
1262
- const f = await fetchNext(postureClient());
1263
- if (!f) {
1264
- console.log(green(' Queue is clear — no open coverage gaps.'));
1265
- return;
1266
- }
1267
- console.log();
1268
- printFinding(f, ' ');
1269
- if (f.fix?.type === 'create_policy_draft') {
1270
- console.log(dim(` Fix: draft a ${f.fix.policyType} policy → dashclaw posture resolve ${f.key}`));
1271
- } else if (f.fix?.deepLink) {
1272
- console.log(dim(` Fix: ${f.fix.type} → ${f.fix.deepLink}`));
1273
- }
1274
- console.log();
1275
- } catch (err) {
1276
- console.error(`Error: ${err.message}`);
1277
- process.exit(1);
1278
- }
1279
- }
1280
-
1281
- // -- env command ---------------------------------------------------------------
1282
- //
1283
- // `dashclaw env [--agent <id>] -- <command...>` — managed-secrets delivery.
1284
- // Fetches the delivery-enabled bundle (GET /api/secrets/env) and spawns the
1285
- // child command with the values injected into its environment MEMORY-ONLY.
1286
- // Secret VALUES are never printed, never written to disk, never echoed in
1287
- // error paths; only NAMES are ever shown. Fail-closed: if the fetch fails,
1288
- // the child is NOT run. Uses process.exitCode (not process.exit) so node
1289
- // drains sockets — a hard exit can trip a libuv teardown assert on Windows.
1290
-
1291
- async function cmdEnv() {
1292
- const { flags, command: childArgv } = splitEnvArgv(args.slice(1));
1293
-
1294
- if (flags.includes('--print')) {
1295
- console.error('Error: --print is not supported. Secret values are memory-only by design —');
1296
- console.error('they are injected into a child process environment, never printed or written to disk.');
1297
- console.error('Use `dashclaw env` to list secret NAMES, or `dashclaw env -- <command>` to run with them.');
1298
- process.exitCode = 1;
1299
- return;
1300
- }
1301
-
1302
- let agent = agentId;
1303
- for (let i = 0; i < flags.length; i++) {
1304
- if (flags[i] === '--agent') agent = flags[i + 1];
1305
- else if (flags[i].startsWith('--agent=')) agent = flags[i].slice('--agent='.length);
1306
- }
1307
- if (!agent) {
1308
- console.error('Error: no agent id. Pass --agent <id> or set DASHCLAW_AGENT_ID.');
1309
- process.exitCode = 1;
1310
- return;
1311
- }
1312
-
1313
- let bundle;
1314
- try {
1315
- bundle = await fetchAgentEnv({ baseUrl, apiKey }, agent);
1316
- } catch (err) {
1317
- console.error(`Error: could not fetch the managed-secrets bundle: ${err.message}`);
1318
- if (childArgv.length > 0) {
1319
- console.error('Fail-closed: the command was NOT run without the requested env.');
1320
- }
1321
- process.exitCode = 1;
1322
- return;
1323
- }
1324
-
1325
- if (childArgv.length === 0) {
1326
- console.log(formatEnvNames(bundle));
1327
- return;
1328
- }
1329
-
1330
- process.exitCode = await runWithEnv(bundle, childArgv);
1331
- }
1332
-
1333
576
  // -- Router -------------------------------------------------------------------
1334
577
 
1335
- const COMMANDS_NEEDING_CONFIG = new Set(['approvals', 'approve', 'deny', 'doctor', 'code', 'prompts', 'inbox', 'behavior', 'posture', 'next', 'env', 'halt', 'import']);
578
+ const COMMANDS_NEEDING_CONFIG = new Set(['approvals', 'approve', 'deny', 'doctor', 'halt', 'import']);
1336
579
  // `install` deliberately omitted: provisioning hooks and AGENTS.md shouldn't
1337
580
  // require the user to have already configured API keys. If config happens to
1338
581
  // be present, install will pick up baseUrl for the AGENTS.md instance link.
1339
582
  // `codex notify` also opt-in: if no config, the notify fail-softs to skipped
1340
583
  // rather than erroring (Codex never sees the error anyway — it spawns with
1341
584
  // stdio nulled).
1342
- // `cost` resolves config non-interactively and prints its own setup hint when
1343
- // missing (the generic interactive prompt would be the wrong UX for a
1344
- // read-only readback command).
1345
- const COMMANDS_OPTIONAL_CONFIG = new Set(['install', 'codex', 'cost']);
585
+ const COMMANDS_OPTIONAL_CONFIG = new Set(['install', 'codex']);
1346
586
 
1347
587
  function applyConfig(config) {
1348
588
  baseUrl = config.baseUrl;
@@ -1375,20 +615,12 @@ const COMMAND_HANDLERS = {
1375
615
  deny: cmdDeny,
1376
616
  doctor: cmdDoctor,
1377
617
  logout: cmdLogout,
1378
- code: cmdCode,
1379
618
  up: cmdUp,
1380
619
  down: cmdDown,
1381
620
  import: cmdImport,
1382
621
  install: cmdInstall,
1383
622
  codex: cmdCodex,
1384
- prompts: cmdPrompts,
1385
- inbox: cmdInbox,
1386
- behavior: cmdBehavior,
1387
- posture: cmdPosture,
1388
- cost: cmdCost,
1389
623
  halt: cmdHalt,
1390
- next: cmdNext,
1391
- env: cmdEnv,
1392
624
  help: cmdHelp,
1393
625
  '--help': cmdHelp,
1394
626
  '-h': cmdHelp,