@gajae-code/coding-agent 0.6.5 → 0.7.0
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/CHANGELOG.md +29 -0
- package/dist/types/async/job-manager.d.ts +3 -1
- package/dist/types/cli/daemon-cli.d.ts +25 -0
- package/dist/types/cli/notify-cli.d.ts +23 -0
- package/dist/types/cli/setup-cli.d.ts +20 -1
- package/dist/types/commands/daemon.d.ts +41 -0
- package/dist/types/commands/notify.d.ts +41 -0
- package/dist/types/config/model-profile-activation.d.ts +12 -0
- package/dist/types/config/model-profiles.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +38 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/daemon/builtin.d.ts +20 -0
- package/dist/types/daemon/control-types.d.ts +57 -0
- package/dist/types/daemon/runtime.d.ts +25 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +2 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +15 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +14 -0
- package/dist/types/modes/components/oauth-selector.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -2
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/types.d.ts +7 -1
- package/dist/types/notifications/config-commands.d.ts +26 -0
- package/dist/types/notifications/config.d.ts +61 -0
- package/dist/types/notifications/helpers.d.ts +55 -0
- package/dist/types/notifications/html-format.d.ts +62 -0
- package/dist/types/notifications/index.d.ts +28 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +93 -0
- package/dist/types/notifications/telegram-cli.d.ts +19 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/notifications/telegram-daemon-control.d.ts +56 -0
- package/dist/types/notifications/telegram-daemon.d.ts +276 -0
- package/dist/types/notifications/telegram-reference.d.ts +111 -0
- package/dist/types/notifications/threaded-inbound.d.ts +58 -0
- package/dist/types/notifications/threaded-render.d.ts +66 -0
- package/dist/types/notifications/topic-registry.d.ts +67 -0
- package/dist/types/rlm/index.d.ts +12 -0
- package/dist/types/session/agent-session.d.ts +39 -2
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/setup/credential-auto-import.d.ts +63 -0
- package/dist/types/setup/credential-import.d.ts +3 -0
- package/dist/types/setup/host-plugin-setup.d.ts +39 -0
- package/dist/types/tools/ask-answer-registry.d.ts +13 -0
- package/dist/types/tools/index.d.ts +18 -0
- package/dist/types/tools/subagent.d.ts +3 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +3 -0
- package/src/async/job-manager.ts +5 -1
- package/src/cli/daemon-cli.ts +122 -0
- package/src/cli/notify-cli.ts +274 -0
- package/src/cli/setup-cli.ts +173 -84
- package/src/cli.ts +2 -0
- package/src/commands/daemon.ts +47 -0
- package/src/commands/notify.ts +61 -0
- package/src/commands/setup.ts +11 -1
- package/src/config/model-profile-activation.ts +74 -5
- package/src/config/model-profiles.ts +7 -4
- package/src/config/model-registry.ts +6 -3
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +29 -0
- package/src/coordinator/contract.ts +3 -0
- package/src/coordinator-mcp/server.ts +270 -1
- package/src/daemon/builtin.ts +46 -0
- package/src/daemon/control-types.ts +65 -0
- package/src/daemon/runtime.ts +51 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +8 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +12 -4
- package/src/gjc-runtime/state-runtime.ts +18 -4
- package/src/gjc-runtime/state-writer.ts +8 -8
- package/src/gjc-runtime/ultragoal-guard.ts +57 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +105 -19
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -2
- package/src/gjc-runtime/workflow-manifest.ts +11 -1
- package/src/goals/tools/goal-tool.ts +11 -2
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/main.ts +30 -0
- package/src/modes/acp/acp-event-mapper.ts +1 -0
- package/src/modes/components/hook-editor.ts +7 -2
- package/src/modes/components/oauth-selector.ts +19 -0
- package/src/modes/controllers/event-controller.ts +20 -0
- package/src/modes/controllers/selector-controller.ts +80 -17
- package/src/modes/interactive-mode.ts +6 -2
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/event-contract.ts +1 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +1 -0
- package/src/modes/shared/agent-wire/event-observation.ts +16 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +22 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/utils/ui-helpers.ts +23 -0
- package/src/notifications/config-commands.ts +50 -0
- package/src/notifications/config.ts +107 -0
- package/src/notifications/helpers.ts +135 -0
- package/src/notifications/html-format.ts +389 -0
- package/src/notifications/index.ts +663 -0
- package/src/notifications/rate-limit-pool.ts +179 -0
- package/src/notifications/telegram-cli.ts +194 -0
- package/src/notifications/telegram-daemon-cli.ts +74 -0
- package/src/notifications/telegram-daemon-control.ts +370 -0
- package/src/notifications/telegram-daemon.ts +1370 -0
- package/src/notifications/telegram-reference.ts +335 -0
- package/src/notifications/threaded-inbound.ts +80 -0
- package/src/notifications/threaded-render.ts +155 -0
- package/src/notifications/topic-registry.ts +133 -0
- package/src/rlm/index.ts +19 -0
- package/src/sdk.ts +16 -0
- package/src/session/agent-session.ts +113 -3
- package/src/session/auth-storage.ts +3 -0
- package/src/session/session-dump-format.ts +43 -2
- package/src/session/session-manager.ts +39 -5
- package/src/setup/credential-auto-import.ts +258 -0
- package/src/setup/credential-import.ts +17 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +10 -0
- package/src/setup/host-plugin-setup.ts +142 -0
- package/src/slash-commands/builtin-registry.ts +4 -1
- package/src/task/executor.ts +5 -1
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +74 -4
- package/src/tools/image-gen.ts +5 -8
- package/src/tools/index.ts +19 -0
- package/src/tools/inspect-image.ts +16 -11
- package/src/tools/subagent-render.ts +7 -0
- package/src/tools/subagent.ts +38 -7
package/src/cli/setup-cli.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
|
-
import { SqliteAuthCredentialStore } from "@gajae-code/ai";
|
|
9
|
+
import { AuthStorage, SqliteAuthCredentialStore } from "@gajae-code/ai";
|
|
10
10
|
import { $which, APP_NAME, getAgentDbPath, getPythonEnvDir } from "@gajae-code/utils";
|
|
11
11
|
import { $ } from "bun";
|
|
12
12
|
import chalk from "chalk";
|
|
@@ -17,13 +17,15 @@ import {
|
|
|
17
17
|
readGjcManagedCodexHooksStatus,
|
|
18
18
|
} from "../hooks/codex-native-hooks-config";
|
|
19
19
|
import { theme } from "../modes/theme/theme";
|
|
20
|
-
import {
|
|
20
|
+
import { formatCredentialAutoImportResult, runExternalCredentialAutoImport } from "../setup/credential-auto-import";
|
|
21
|
+
import { filterAutoImportOAuthCredentials, formatDiscoverySummary } from "../setup/credential-import";
|
|
21
22
|
import {
|
|
22
23
|
formatHermesSetupResult,
|
|
23
24
|
type HermesSetupFlags,
|
|
24
25
|
hermesSetupExitCode,
|
|
25
26
|
runHermesSetup,
|
|
26
27
|
} from "../setup/hermes-setup";
|
|
28
|
+
import { buildHostPluginSetup, formatHostPluginSetup, type HostPluginKind } from "../setup/host-plugin-setup";
|
|
27
29
|
import {
|
|
28
30
|
addApiCompatibleProvider,
|
|
29
31
|
formatProviderPresetList,
|
|
@@ -31,7 +33,16 @@ import {
|
|
|
31
33
|
parseProviderCompatibility,
|
|
32
34
|
} from "../setup/provider-onboarding";
|
|
33
35
|
|
|
34
|
-
export type SetupComponent =
|
|
36
|
+
export type SetupComponent =
|
|
37
|
+
| "claude"
|
|
38
|
+
| "codex"
|
|
39
|
+
| "credentials"
|
|
40
|
+
| "defaults"
|
|
41
|
+
| "hermes"
|
|
42
|
+
| "hooks"
|
|
43
|
+
| "provider"
|
|
44
|
+
| "python"
|
|
45
|
+
| "stt";
|
|
35
46
|
|
|
36
47
|
export interface SetupCommandArgs {
|
|
37
48
|
component: SetupComponent;
|
|
@@ -63,10 +74,21 @@ export interface SetupCommandArgs {
|
|
|
63
74
|
profileDir?: string;
|
|
64
75
|
yes?: boolean;
|
|
65
76
|
dryRun?: boolean;
|
|
77
|
+
keychain?: boolean;
|
|
66
78
|
};
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
const VALID_COMPONENTS: SetupComponent[] = [
|
|
81
|
+
const VALID_COMPONENTS: SetupComponent[] = [
|
|
82
|
+
"claude",
|
|
83
|
+
"codex",
|
|
84
|
+
"credentials",
|
|
85
|
+
"defaults",
|
|
86
|
+
"hermes",
|
|
87
|
+
"hooks",
|
|
88
|
+
"provider",
|
|
89
|
+
"python",
|
|
90
|
+
"stt",
|
|
91
|
+
];
|
|
70
92
|
|
|
71
93
|
function hasProviderSetupFlags(flags: SetupCommandArgs["flags"]): boolean {
|
|
72
94
|
return (
|
|
@@ -123,6 +145,8 @@ export function parseSetupArgs(args: string[]): SetupCommandArgs | undefined {
|
|
|
123
145
|
flags.yes = true;
|
|
124
146
|
} else if (arg === "--dry-run") {
|
|
125
147
|
flags.dryRun = true;
|
|
148
|
+
} else if (arg === "--keychain") {
|
|
149
|
+
flags.keychain = true;
|
|
126
150
|
} else if (arg === "--root") {
|
|
127
151
|
flags.root = [...(flags.root ?? []), args[++i] ?? ""];
|
|
128
152
|
} else if (arg === "--repo") {
|
|
@@ -235,6 +259,12 @@ async function checkPythonSetup(): Promise<PythonCheckResult> {
|
|
|
235
259
|
export async function runSetupCommand(cmd: SetupCommandArgs): Promise<void> {
|
|
236
260
|
rejectProviderFlagsOutsideProvider(cmd.component, cmd.flags);
|
|
237
261
|
switch (cmd.component) {
|
|
262
|
+
case "claude":
|
|
263
|
+
handleHostPluginSetup("claude", cmd.flags);
|
|
264
|
+
break;
|
|
265
|
+
case "codex":
|
|
266
|
+
handleHostPluginSetup("codex", cmd.flags);
|
|
267
|
+
break;
|
|
238
268
|
case "defaults":
|
|
239
269
|
await handleDefaultsSetup(cmd.flags);
|
|
240
270
|
break;
|
|
@@ -279,6 +309,22 @@ async function handleHermesSetup(flags: HermesSetupFlags): Promise<void> {
|
|
|
279
309
|
process.exit(hermesSetupExitCode(error));
|
|
280
310
|
}
|
|
281
311
|
}
|
|
312
|
+
|
|
313
|
+
function handleHostPluginSetup(host: HostPluginKind, flags: SetupCommandArgs["flags"]): void {
|
|
314
|
+
const result = buildHostPluginSetup(host, {
|
|
315
|
+
json: flags.json,
|
|
316
|
+
check: flags.check,
|
|
317
|
+
root: flags.root,
|
|
318
|
+
repo: flags.repo,
|
|
319
|
+
});
|
|
320
|
+
if (flags.json) {
|
|
321
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const label = host === "claude" ? "Claude Code" : "Codex";
|
|
325
|
+
process.stdout.write(`${chalk.green(`${theme.status.success} ${label} plugin setup ready`)}\n`);
|
|
326
|
+
process.stdout.write(`${chalk.dim(formatHostPluginSetup(result))}\n`);
|
|
327
|
+
}
|
|
282
328
|
async function handleProviderSetup(flags: {
|
|
283
329
|
json?: boolean;
|
|
284
330
|
force?: boolean;
|
|
@@ -516,99 +562,141 @@ async function confirmImport(count: number): Promise<boolean> {
|
|
|
516
562
|
* gjc credential store after a redacted preview + confirmation. Falls back to
|
|
517
563
|
* manual-setup guidance when nothing importable is found.
|
|
518
564
|
*/
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
kind: c.kind,
|
|
525
|
-
source: c.source,
|
|
526
|
-
identity: c.identity,
|
|
527
|
-
expiresAt: c.expiresAt,
|
|
528
|
-
redactedToken: c.redactedToken,
|
|
529
|
-
})),
|
|
530
|
-
skipped: result.skipped,
|
|
531
|
-
environment: result.environment,
|
|
532
|
-
};
|
|
565
|
+
export interface CredentialsSetupDependencies {
|
|
566
|
+
openStore?: typeof SqliteAuthCredentialStore.open;
|
|
567
|
+
createAuthStorage?: (store: Awaited<ReturnType<typeof SqliteAuthCredentialStore.open>>) => AuthStorage;
|
|
568
|
+
discover?: Parameters<typeof runExternalCredentialAutoImport>[0]["discover"];
|
|
569
|
+
}
|
|
533
570
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
571
|
+
export async function handleCredentialsSetup(
|
|
572
|
+
flags: {
|
|
573
|
+
json?: boolean;
|
|
574
|
+
yes?: boolean;
|
|
575
|
+
dryRun?: boolean;
|
|
576
|
+
keychain?: boolean;
|
|
577
|
+
},
|
|
578
|
+
deps: CredentialsSetupDependencies = {},
|
|
579
|
+
): Promise<void> {
|
|
580
|
+
const discoveryOptions = flags.keychain ? undefined : { readClaudeKeychain: async () => null };
|
|
581
|
+
const store = await (deps.openStore ?? SqliteAuthCredentialStore.open)(getAgentDbPath());
|
|
582
|
+
const authStorage = deps.createAuthStorage?.(store) ?? new AuthStorage(store);
|
|
583
|
+
await authStorage.reload();
|
|
584
|
+
try {
|
|
585
|
+
const preview = await runExternalCredentialAutoImport({
|
|
586
|
+
authStorage: {
|
|
587
|
+
importCredentialIfAbsent: async () => ({
|
|
588
|
+
inserted: false,
|
|
589
|
+
reason: "skipped-existing",
|
|
590
|
+
provider: "",
|
|
591
|
+
entries: [],
|
|
592
|
+
}),
|
|
593
|
+
},
|
|
594
|
+
discover: deps.discover,
|
|
595
|
+
discoveryOptions,
|
|
596
|
+
trigger: "setup-cli",
|
|
597
|
+
});
|
|
598
|
+
const result = preview.discovery ?? { importable: [], skipped: [], environment: [] };
|
|
599
|
+
const candidates = filterAutoImportOAuthCredentials(result.importable);
|
|
600
|
+
const filteredResult = { ...result, importable: candidates };
|
|
601
|
+
const redactedPlan = {
|
|
602
|
+
importable: candidates.map(c => ({
|
|
603
|
+
provider: c.provider,
|
|
604
|
+
kind: c.kind,
|
|
605
|
+
source: c.source,
|
|
606
|
+
identity: c.identity,
|
|
607
|
+
expiresAt: c.expiresAt,
|
|
608
|
+
redactedToken: c.redactedToken,
|
|
609
|
+
})),
|
|
610
|
+
skipped: result.skipped,
|
|
611
|
+
environment: result.environment,
|
|
612
|
+
keychainChecked: flags.keychain === true,
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
if (!flags.keychain && !flags.json) {
|
|
616
|
+
process.stdout.write(chalk.dim("Claude Keychain not checked (pass --keychain to include it)\n"));
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (candidates.length === 0) {
|
|
620
|
+
if (flags.json) {
|
|
621
|
+
process.stdout.write(`${JSON.stringify({ ...redactedPlan, imported: [] })}\n`);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
for (const line of formatDiscoverySummary(filteredResult)) process.stdout.write(` ${line}\n`);
|
|
625
|
+
process.stdout.write(
|
|
626
|
+
chalk.yellow(
|
|
627
|
+
`\nNo importable Claude/Codex credentials found. Continue with manual setup:\n` +
|
|
628
|
+
` ${APP_NAME} setup provider (add an API-compatible provider)\n` +
|
|
629
|
+
` ${APP_NAME} (then /login) (interactive OAuth/subscription login)\n`,
|
|
630
|
+
),
|
|
631
|
+
);
|
|
537
632
|
return;
|
|
538
633
|
}
|
|
539
|
-
for (const line of formatDiscoverySummary(result)) process.stdout.write(` ${line}\n`);
|
|
540
|
-
process.stdout.write(
|
|
541
|
-
chalk.yellow(
|
|
542
|
-
`\nNo importable Claude/Codex credentials found. Continue with manual setup:\n` +
|
|
543
|
-
` ${APP_NAME} setup provider (add an API-compatible provider)\n` +
|
|
544
|
-
` ${APP_NAME} (then /login) (interactive OAuth/subscription login)\n`,
|
|
545
|
-
),
|
|
546
|
-
);
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
634
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
635
|
+
if (!flags.json) {
|
|
636
|
+
process.stdout.write(chalk.bold("Discovered credentials (redacted):\n"));
|
|
637
|
+
for (const line of formatDiscoverySummary(filteredResult)) process.stdout.write(` ${line}\n`);
|
|
638
|
+
}
|
|
554
639
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
640
|
+
if (flags.dryRun) {
|
|
641
|
+
if (flags.json) process.stdout.write(`${JSON.stringify({ ...redactedPlan, dryRun: true, imported: [] })}\n`);
|
|
642
|
+
else process.stdout.write(chalk.dim(`\nDry run — no credentials imported.\n`));
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const confirmed = flags.yes || (await confirmImport(candidates.length));
|
|
647
|
+
if (!confirmed) {
|
|
648
|
+
if (flags.json) {
|
|
649
|
+
process.stdout.write(`${JSON.stringify({ ...redactedPlan, imported: [] })}\n`);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
process.stdout.write(chalk.dim(`\nImport cancelled. Re-run with --yes to import non-interactively.\n`));
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const summary = await runExternalCredentialAutoImport({
|
|
657
|
+
authStorage,
|
|
658
|
+
discover: deps.discover,
|
|
659
|
+
discoveryOptions,
|
|
660
|
+
trigger: "setup-cli",
|
|
661
|
+
});
|
|
560
662
|
|
|
561
|
-
const confirmed = flags.yes || (await confirmImport(result.importable.length));
|
|
562
|
-
if (!confirmed) {
|
|
563
663
|
if (flags.json) {
|
|
564
|
-
process.stdout.write(
|
|
664
|
+
process.stdout.write(
|
|
665
|
+
`${JSON.stringify({
|
|
666
|
+
...redactedPlan,
|
|
667
|
+
imported: summary.imported.map(c => ({ provider: c.provider, kind: c.kind, source: c.source })),
|
|
668
|
+
skippedImport: summary.skipped.map(s => ({
|
|
669
|
+
provider: s.credential.provider,
|
|
670
|
+
source: s.credential.source,
|
|
671
|
+
reason: s.reason,
|
|
672
|
+
})),
|
|
673
|
+
failed: summary.failures.map(f => ({
|
|
674
|
+
provider: f.credential?.provider,
|
|
675
|
+
source: f.credential?.source ?? f.source,
|
|
676
|
+
error: f.failureClass,
|
|
677
|
+
})),
|
|
678
|
+
})}\n`,
|
|
679
|
+
);
|
|
680
|
+
if (summary.failures.length > 0) process.exitCode = 1;
|
|
565
681
|
return;
|
|
566
682
|
}
|
|
567
|
-
process.stdout.write(chalk.dim(`\nImport cancelled. Re-run with --yes to import non-interactively.\n`));
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
683
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
)
|
|
684
|
+
for (const credential of summary.imported) {
|
|
685
|
+
process.stdout.write(
|
|
686
|
+
`${chalk.green(`${theme.status.success} imported`)} ${formatCredentialSummaryLine(credential)}\n`,
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
for (const line of formatCredentialAutoImportResult({ ...summary, imported: [], skipped: [] })) {
|
|
690
|
+
process.stdout.write(`${chalk.dim(line)}\n`);
|
|
691
|
+
}
|
|
692
|
+
if (summary.failures.length > 0) {
|
|
693
|
+
process.exitCode = 1;
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
process.stdout.write(chalk.dim(`\nCredentials saved to ${getAgentDbPath()}\n`));
|
|
577
697
|
} finally {
|
|
578
698
|
store.close();
|
|
579
699
|
}
|
|
580
|
-
|
|
581
|
-
if (flags.json) {
|
|
582
|
-
process.stdout.write(
|
|
583
|
-
`${JSON.stringify({
|
|
584
|
-
...redactedPlan,
|
|
585
|
-
imported: summary.imported.map(c => ({ provider: c.provider, kind: c.kind, source: c.source })),
|
|
586
|
-
failed: summary.failed.map(f => ({
|
|
587
|
-
provider: f.credential.provider,
|
|
588
|
-
source: f.credential.source,
|
|
589
|
-
error: f.error,
|
|
590
|
-
})),
|
|
591
|
-
})}\n`,
|
|
592
|
-
);
|
|
593
|
-
if (summary.failed.length > 0) process.exitCode = 1;
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
for (const credential of summary.imported) {
|
|
598
|
-
process.stdout.write(
|
|
599
|
-
`${chalk.green(`${theme.status.success} imported`)} ${formatCredentialSummaryLine(credential)}\n`,
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
for (const failure of summary.failed) {
|
|
603
|
-
process.stdout.write(
|
|
604
|
-
`${chalk.red(`${theme.status.error} failed`)} ${failure.credential.provider} (${failure.credential.source}): ${failure.error}\n`,
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
if (summary.failed.length > 0) {
|
|
608
|
-
process.exitCode = 1;
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
process.stdout.write(chalk.dim(`\nCredentials saved to ${getAgentDbPath()}\n`));
|
|
612
700
|
}
|
|
613
701
|
|
|
614
702
|
function formatCredentialSummaryLine(credential: { provider: string; kind: string; source: string }): string {
|
|
@@ -669,6 +757,7 @@ ${chalk.bold("Options:")}
|
|
|
669
757
|
--profile-dir Hermes profile directory for full setup install
|
|
670
758
|
--dry-run Preview discovered credentials without importing (credentials)
|
|
671
759
|
-y, --yes Import discovered credentials without an interactive prompt (credentials)
|
|
760
|
+
--keychain Include Claude macOS Keychain when discovering credentials
|
|
672
761
|
|
|
673
762
|
${chalk.bold("Examples:")}
|
|
674
763
|
${APP_NAME} setup Install bundled GJC default workflow skills
|
package/src/cli.ts
CHANGED
|
@@ -35,6 +35,8 @@ const commands: CommandEntry[] = [
|
|
|
35
35
|
{ name: "gc", load: () => import("./commands/gc").then(m => m.default) },
|
|
36
36
|
{ name: "ralplan", load: () => import("./commands/ralplan").then(m => m.default) },
|
|
37
37
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
38
|
+
{ name: "notify", load: () => import("./commands/notify").then(m => m.default) },
|
|
39
|
+
{ name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
|
|
38
40
|
{ name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
|
|
39
41
|
{ name: "mcp-serve", load: () => import("./commands/mcp-serve").then(m => m.default) },
|
|
40
42
|
{
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manage GJC background daemons (status/list/stop/reload).
|
|
3
|
+
*/
|
|
4
|
+
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
5
|
+
import { type DaemonCliAction, type DaemonCommandArgs, runDaemonCommand } from "../cli/daemon-cli";
|
|
6
|
+
import type { DaemonKind } from "../daemon/control-types";
|
|
7
|
+
import { initTheme } from "../modes/theme/theme";
|
|
8
|
+
|
|
9
|
+
const ACTIONS: DaemonCliAction[] = ["list", "status", "stop", "reload"];
|
|
10
|
+
|
|
11
|
+
export default class Daemon extends Command {
|
|
12
|
+
static description = "Manage GJC background daemons (status, list, stop, reload)";
|
|
13
|
+
|
|
14
|
+
static args = {
|
|
15
|
+
action: Args.string({ description: "Daemon action", required: false, options: ACTIONS }),
|
|
16
|
+
kind: Args.string({ description: "Daemon kind(s) to target", required: false, multiple: true }),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
static flags = {
|
|
20
|
+
all: Flags.boolean({ description: "Target all registered daemon kinds" }),
|
|
21
|
+
json: Flags.boolean({ description: "Emit JSON output" }),
|
|
22
|
+
force: Flags.boolean({ description: "Allow hard-kill escalation when graceful stop times out" }),
|
|
23
|
+
"graceful-timeout-ms": Flags.integer({ description: "Cooperative stop timeout before escalation" }),
|
|
24
|
+
"kill-timeout-ms": Flags.integer({ description: "Wait for old pid death after SIGKILL" }),
|
|
25
|
+
"spawn-if-stopped": Flags.boolean({ description: "On reload, spawn even when no daemon is running" }),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
async run(): Promise<void> {
|
|
29
|
+
const { args, flags } = await this.parse(Daemon);
|
|
30
|
+
const action = (args.action ?? "status") as DaemonCliAction;
|
|
31
|
+
const kinds = (Array.isArray(args.kind) ? args.kind : args.kind ? [args.kind] : []) as DaemonKind[];
|
|
32
|
+
const flagRec = flags as Record<string, unknown>;
|
|
33
|
+
const cmd: DaemonCommandArgs = {
|
|
34
|
+
action,
|
|
35
|
+
kinds,
|
|
36
|
+
all: Boolean(flags.all),
|
|
37
|
+
json: Boolean(flags.json),
|
|
38
|
+
force: Boolean(flags.force),
|
|
39
|
+
gracefulTimeoutMs: flagRec["graceful-timeout-ms"] as number | undefined,
|
|
40
|
+
killTimeoutMs: flagRec["kill-timeout-ms"] as number | undefined,
|
|
41
|
+
spawnIfStopped: flagRec["spawn-if-stopped"] as boolean | undefined,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
await initTheme();
|
|
45
|
+
await runDaemonCommand(cmd);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configure Telegram notifications.
|
|
3
|
+
*/
|
|
4
|
+
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
5
|
+
import { type NotifyAction, type NotifyCommandArgs, runNotifyCommand } from "../cli/notify-cli";
|
|
6
|
+
import { initTheme } from "../modes/theme/theme";
|
|
7
|
+
|
|
8
|
+
const ACTIONS: NotifyAction[] = ["setup", "status", "daemon-internal"];
|
|
9
|
+
|
|
10
|
+
export default class Notify extends Command {
|
|
11
|
+
static description = "Configure Telegram notifications";
|
|
12
|
+
|
|
13
|
+
static args = {
|
|
14
|
+
action: Args.string({
|
|
15
|
+
description: "Notify action",
|
|
16
|
+
required: false,
|
|
17
|
+
options: ACTIONS,
|
|
18
|
+
}),
|
|
19
|
+
extra: Args.string({
|
|
20
|
+
description: "Additional internal args",
|
|
21
|
+
required: false,
|
|
22
|
+
multiple: true,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
static flags = {
|
|
27
|
+
smoke: Flags.boolean({ description: "Run hidden daemon smoke" }),
|
|
28
|
+
token: Flags.string({ description: "Telegram bot token (non-interactive setup)" }),
|
|
29
|
+
"chat-id": Flags.string({ description: "Telegram chat id to pair (non-interactive setup)" }),
|
|
30
|
+
redact: Flags.boolean({ description: "Enable redaction of remote notification content" }),
|
|
31
|
+
"owner-id": Flags.string({ description: "Internal: daemon owner id" }),
|
|
32
|
+
"agent-dir": Flags.string({ description: "Internal: agent dir for the daemon" }),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async run(): Promise<void> {
|
|
36
|
+
const { args, flags } = await this.parse(Notify);
|
|
37
|
+
const action = (args.action ?? "status") as NotifyAction;
|
|
38
|
+
const extra = Array.isArray(args.extra) ? args.extra : args.extra ? [args.extra] : [];
|
|
39
|
+
const flagRec = flags as Record<string, unknown>;
|
|
40
|
+
const ownerId = flagRec["owner-id"] as string | undefined;
|
|
41
|
+
const agentDir = flagRec["agent-dir"] as string | undefined;
|
|
42
|
+
const rawArgs = [
|
|
43
|
+
...(flags.smoke ? ["--smoke"] : []),
|
|
44
|
+
...(ownerId ? ["--owner-id", ownerId] : []),
|
|
45
|
+
...(agentDir ? ["--agent-dir", agentDir] : []),
|
|
46
|
+
...extra,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const cmd: NotifyCommandArgs = {
|
|
50
|
+
action,
|
|
51
|
+
smoke: flags.smoke,
|
|
52
|
+
rawArgs,
|
|
53
|
+
token: flags.token as string | undefined,
|
|
54
|
+
chatId: (flags as Record<string, unknown>)["chat-id"] as string | undefined,
|
|
55
|
+
redact: Boolean(flags.redact),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
await initTheme();
|
|
59
|
+
await runNotifyCommand(cmd);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/commands/setup.ts
CHANGED
|
@@ -5,7 +5,17 @@ import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
|
5
5
|
import { runSetupCommand, type SetupCommandArgs, type SetupComponent } from "../cli/setup-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
|
8
|
-
const COMPONENTS: SetupComponent[] = [
|
|
8
|
+
const COMPONENTS: SetupComponent[] = [
|
|
9
|
+
"claude",
|
|
10
|
+
"codex",
|
|
11
|
+
"credentials",
|
|
12
|
+
"defaults",
|
|
13
|
+
"hermes",
|
|
14
|
+
"hooks",
|
|
15
|
+
"provider",
|
|
16
|
+
"python",
|
|
17
|
+
"stt",
|
|
18
|
+
];
|
|
9
19
|
|
|
10
20
|
export default class Setup extends Command {
|
|
11
21
|
static description = "Install GJC defaults or optional feature dependencies";
|
|
@@ -17,6 +17,8 @@ type ModelProfileActivationSession = Pick<AgentSession, "model" | "thinkingLevel
|
|
|
17
17
|
setModelTemporary?: AgentSession["setModelTemporary"];
|
|
18
18
|
setActiveModelProfile?: (name: string | undefined) => void;
|
|
19
19
|
getActiveModelProfile?: () => string | undefined;
|
|
20
|
+
getSessionDefaultModelSelector?: () => string | undefined;
|
|
21
|
+
recordResumeDefaultModel?: (selector: string) => void;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
export interface PrepareModelProfileActivationOptions {
|
|
@@ -43,10 +45,20 @@ export interface PreparedModelProfileActivation {
|
|
|
43
45
|
previousModel: Model<Api> | undefined;
|
|
44
46
|
previousThinkingLevel: ThinkingLevel | undefined;
|
|
45
47
|
previousAgentModelOverrides: Record<string, string>;
|
|
48
|
+
previousModelRoles: Record<string, string>;
|
|
46
49
|
defaultModel: Model<Api> | undefined;
|
|
47
50
|
defaultThinkingLevel: ThinkingLevel | undefined;
|
|
51
|
+
modelRoles: Record<string, string>;
|
|
48
52
|
agentModelOverrides: Record<string, string>;
|
|
49
53
|
previousActiveModelProfile: string | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* The session resume default ("provider/id") captured BEFORE activation —
|
|
56
|
+
* the model resume would restore prior to this profile. Snapshotted
|
|
57
|
+
* separately from `previousModel` (the live runtime model, which may be a
|
|
58
|
+
* transient switch) so a failed-activation rollback restores the correct
|
|
59
|
+
* resume default without promoting a transient model to it.
|
|
60
|
+
*/
|
|
61
|
+
previousSessionDefaultModel: string | undefined;
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
export function formatModelProfileCredentialError(profileName: string, providers: readonly string[]): string {
|
|
@@ -87,14 +99,24 @@ function rewriteSelectorProvider(
|
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
function rewriteBindingsProviders(
|
|
90
|
-
bindings: {
|
|
102
|
+
bindings: {
|
|
103
|
+
defaultSelector?: string;
|
|
104
|
+
modelRoles: Record<string, string>;
|
|
105
|
+
agentModelOverrides: Record<string, string>;
|
|
106
|
+
},
|
|
91
107
|
authenticatedProviders: ReadonlySet<string>,
|
|
92
108
|
alternativeGroups: readonly (readonly string[])[],
|
|
93
|
-
): { defaultSelector?: string; agentModelOverrides: Record<string, string> } {
|
|
109
|
+
): { defaultSelector?: string; modelRoles: Record<string, string>; agentModelOverrides: Record<string, string> } {
|
|
94
110
|
return {
|
|
95
111
|
defaultSelector: bindings.defaultSelector
|
|
96
112
|
? rewriteSelectorProvider(bindings.defaultSelector, authenticatedProviders, alternativeGroups)
|
|
97
113
|
: undefined,
|
|
114
|
+
modelRoles: Object.fromEntries(
|
|
115
|
+
Object.entries(bindings.modelRoles).map(([role, sel]) => [
|
|
116
|
+
role,
|
|
117
|
+
rewriteSelectorProvider(sel, authenticatedProviders, alternativeGroups),
|
|
118
|
+
]),
|
|
119
|
+
),
|
|
98
120
|
agentModelOverrides: Object.fromEntries(
|
|
99
121
|
Object.entries(bindings.agentModelOverrides).map(([role, sel]) => [
|
|
100
122
|
role,
|
|
@@ -165,6 +187,18 @@ export async function prepareModelProfileActivation(
|
|
|
165
187
|
);
|
|
166
188
|
}
|
|
167
189
|
|
|
190
|
+
const modelRoles: Record<string, string> = {};
|
|
191
|
+
for (const [role, selector] of Object.entries(bindings.modelRoles) as [GjcModelAssignmentTargetId, string][]) {
|
|
192
|
+
const resolved = resolveModelRoleValue(selector, availableModels, {
|
|
193
|
+
settings: options.settings as Settings,
|
|
194
|
+
modelRegistry: options.modelRegistry,
|
|
195
|
+
});
|
|
196
|
+
if (!resolved.model) {
|
|
197
|
+
throw new Error(`Model profile "${options.profileName}" ${role} selector did not resolve: ${selector}`);
|
|
198
|
+
}
|
|
199
|
+
modelRoles[role] = formatClampedModelSelector(selector, resolved.model);
|
|
200
|
+
}
|
|
201
|
+
|
|
168
202
|
const agentModelOverrides: Record<string, string> = {};
|
|
169
203
|
for (const [role, selector] of Object.entries(bindings.agentModelOverrides) as [
|
|
170
204
|
GjcModelAssignmentTargetId,
|
|
@@ -187,10 +221,13 @@ export async function prepareModelProfileActivation(
|
|
|
187
221
|
previousModel: options.session.model,
|
|
188
222
|
previousThinkingLevel: options.session.thinkingLevel,
|
|
189
223
|
previousAgentModelOverrides: { ...options.settings.get("task.agentModelOverrides") },
|
|
224
|
+
previousModelRoles: { ...options.settings.get("modelRoles") },
|
|
190
225
|
defaultModel: resolvedDefault?.model,
|
|
191
226
|
defaultThinkingLevel: resolvedDefault?.thinkingLevel,
|
|
227
|
+
modelRoles,
|
|
192
228
|
agentModelOverrides,
|
|
193
229
|
previousActiveModelProfile: options.session.getActiveModelProfile?.(),
|
|
230
|
+
previousSessionDefaultModel: options.session.getSessionDefaultModelSelector?.(),
|
|
194
231
|
};
|
|
195
232
|
}
|
|
196
233
|
|
|
@@ -201,17 +238,29 @@ export async function applyPreparedModelProfileActivation(
|
|
|
201
238
|
const previousModel = prepared.previousModel;
|
|
202
239
|
const previousThinkingLevel = prepared.previousThinkingLevel;
|
|
203
240
|
const previousAgentModelOverrides = prepared.previousAgentModelOverrides;
|
|
241
|
+
const previousModelRoles = prepared.previousModelRoles;
|
|
204
242
|
const previousPersistedDefault = prepared.settings.get("modelProfile.default");
|
|
205
243
|
const previousActiveModelProfile = prepared.previousActiveModelProfile;
|
|
244
|
+
const previousSessionDefaultModel = prepared.previousSessionDefaultModel;
|
|
206
245
|
let modelChanged = false;
|
|
207
246
|
let overridesChanged = false;
|
|
208
247
|
let defaultChanged = false;
|
|
248
|
+
let modelRolesChanged = false;
|
|
209
249
|
|
|
210
250
|
try {
|
|
211
251
|
if (prepared.defaultModel) {
|
|
212
|
-
await prepared.session.setModelTemporary(prepared.defaultModel, prepared.defaultThinkingLevel
|
|
252
|
+
await prepared.session.setModelTemporary(prepared.defaultModel, prepared.defaultThinkingLevel, {
|
|
253
|
+
persistAsSessionDefault: true,
|
|
254
|
+
});
|
|
213
255
|
modelChanged = true;
|
|
214
256
|
}
|
|
257
|
+
if (Object.keys(prepared.modelRoles).length > 0) {
|
|
258
|
+
prepared.settings.override("modelRoles", {
|
|
259
|
+
...prepared.settings.get("modelRoles"),
|
|
260
|
+
...prepared.modelRoles,
|
|
261
|
+
});
|
|
262
|
+
modelRolesChanged = true;
|
|
263
|
+
}
|
|
215
264
|
if (Object.keys(prepared.agentModelOverrides).length > 0) {
|
|
216
265
|
prepared.settings.override("task.agentModelOverrides", {
|
|
217
266
|
...prepared.settings.get("task.agentModelOverrides"),
|
|
@@ -229,12 +278,32 @@ export async function applyPreparedModelProfileActivation(
|
|
|
229
278
|
if (defaultChanged) {
|
|
230
279
|
prepared.settings.set("modelProfile.default", previousPersistedDefault);
|
|
231
280
|
}
|
|
281
|
+
if (modelRolesChanged) {
|
|
282
|
+
prepared.settings.override("modelRoles", previousModelRoles);
|
|
283
|
+
}
|
|
232
284
|
if (overridesChanged) {
|
|
233
285
|
prepared.settings.override("task.agentModelOverrides", previousAgentModelOverrides);
|
|
234
286
|
}
|
|
235
287
|
prepared.session.setActiveModelProfile?.(previousActiveModelProfile);
|
|
236
|
-
if (modelChanged
|
|
237
|
-
|
|
288
|
+
if (modelChanged) {
|
|
289
|
+
// Runtime rolls back to the pre-activation live model. That model may
|
|
290
|
+
// itself be a transient retry/fallback/context-promotion/plan switch,
|
|
291
|
+
// so it is recorded as role:"temporary" (NOT the resume default) to
|
|
292
|
+
// preserve the issue #849 protection.
|
|
293
|
+
if (previousModel) {
|
|
294
|
+
await prepared.session.setModelTemporary(previousModel, previousThinkingLevel);
|
|
295
|
+
}
|
|
296
|
+
// The happy path already appended the profile main model as the resume
|
|
297
|
+
// default (role:"default"). Re-assert the pre-activation resume default
|
|
298
|
+
// so a failed activation does not poison future resume. Fall back to the
|
|
299
|
+
// live model only when there was no explicit pre-activation default
|
|
300
|
+
// (nothing to protect). Append-only — never touches the runtime model.
|
|
301
|
+
const restoreDefaultSelector =
|
|
302
|
+
previousSessionDefaultModel ??
|
|
303
|
+
(previousModel ? `${previousModel.provider}/${previousModel.id}` : undefined);
|
|
304
|
+
if (restoreDefaultSelector) {
|
|
305
|
+
prepared.session.recordResumeDefaultModel?.(restoreDefaultSelector);
|
|
306
|
+
}
|
|
238
307
|
}
|
|
239
308
|
throw error;
|
|
240
309
|
}
|
|
@@ -23,7 +23,8 @@ export interface ModelProfileDefinition {
|
|
|
23
23
|
|
|
24
24
|
export interface ResolvedProfileBinding {
|
|
25
25
|
defaultSelector?: string;
|
|
26
|
-
|
|
26
|
+
modelRoles: Partial<Record<"vision", string>>;
|
|
27
|
+
agentModelOverrides: Partial<Record<Exclude<ModelProfileRole, "default" | "vision">, string>>;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
function parseModelSelectorProvider(selector: string): string | undefined {
|
|
@@ -56,7 +57,7 @@ export function aggregateModelProfileRequiredProviders(
|
|
|
56
57
|
const profile = (
|
|
57
58
|
name: string,
|
|
58
59
|
requiredProviders: string[],
|
|
59
|
-
modelMapping: Record<ModelProfileRole, string
|
|
60
|
+
modelMapping: Partial<Record<ModelProfileRole, string>>,
|
|
60
61
|
alternativeProviderGroups?: readonly (readonly string[])[],
|
|
61
62
|
): ModelProfileDefinition => ({
|
|
62
63
|
name,
|
|
@@ -382,13 +383,15 @@ export function mergeModelProfiles(userProfiles?: ModelsConfig["profiles"]): Map
|
|
|
382
383
|
}
|
|
383
384
|
|
|
384
385
|
export function resolveProfileBindings(definition: ModelProfileDefinition): ResolvedProfileBinding {
|
|
385
|
-
const { default: defaultSelector, executor, architect, planner, critic } = definition.modelMapping;
|
|
386
|
+
const { default: defaultSelector, vision, executor, architect, planner, critic } = definition.modelMapping;
|
|
387
|
+
const modelRoles: ResolvedProfileBinding["modelRoles"] = {};
|
|
388
|
+
if (vision !== undefined) modelRoles.vision = vision;
|
|
386
389
|
const agentModelOverrides: ResolvedProfileBinding["agentModelOverrides"] = {};
|
|
387
390
|
if (executor !== undefined) agentModelOverrides.executor = executor;
|
|
388
391
|
if (architect !== undefined) agentModelOverrides.architect = architect;
|
|
389
392
|
if (planner !== undefined) agentModelOverrides.planner = planner;
|
|
390
393
|
if (critic !== undefined) agentModelOverrides.critic = critic;
|
|
391
|
-
return { defaultSelector, agentModelOverrides };
|
|
394
|
+
return { defaultSelector, modelRoles, agentModelOverrides };
|
|
392
395
|
}
|
|
393
396
|
|
|
394
397
|
export function formatAvailableProfileNames(profiles: ReadonlyMap<string, ModelProfileDefinition>): string {
|