@gajae-code/coding-agent 0.8.0 → 0.8.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/CHANGELOG.md +48 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +12 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/notifications/config.d.ts +8 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +6 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
- package/dist/types/notifications/telegram-daemon.d.ts +18 -3
- package/dist/types/notifications/telegram-reference.d.ts +0 -5
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +10 -0
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +7 -7
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +37 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/notify-cli.ts +91 -7
- package/src/cli.ts +3 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-registry.ts +14 -2
- package/src/config/settings.ts +22 -0
- package/src/coordinator-mcp/server.ts +67 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +38 -5
- package/src/defaults/gjc/skills/ralplan/SKILL.md +15 -11
- package/src/defaults/gjc/skills/team/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +2 -0
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/gjc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +62 -8
- package/src/gjc-runtime/deep-interview-runtime.ts +210 -2
- package/src/gjc-runtime/deep-interview-state.ts +13 -0
- package/src/gjc-runtime/session-state-sidecar.ts +59 -11
- package/src/gjc-runtime/state-runtime.ts +12 -2
- package/src/gjc-runtime/team-runtime.ts +23 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +73 -19
- package/src/hooks/native-skill-hook.ts +29 -0
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +52 -41
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/welcome.ts +160 -44
- package/src/modes/controllers/command-controller.ts +4 -4
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +186 -25
- package/src/modes/controllers/selector-controller.ts +122 -27
- package/src/modes/interactive-mode.ts +43 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +24 -13
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +2 -2
- package/src/notifications/config.ts +9 -1
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +39 -9
- package/src/notifications/lifecycle-commands.ts +37 -7
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/telegram-daemon-control.ts +3 -1
- package/src/notifications/telegram-daemon.ts +231 -43
- package/src/notifications/telegram-reference.ts +0 -16
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/tools/computer.md +2 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +20 -6
- package/src/session/agent-session.ts +225 -31
- package/src/session/agent-storage.ts +4 -0
- package/src/session/session-manager.ts +44 -2
- package/src/slash-commands/builtin-registry.ts +153 -71
- package/src/task/executor.ts +7 -4
- package/src/task/index.ts +9 -3
- package/src/task/output-manager.ts +16 -6
- package/src/task/receipt.ts +26 -7
- package/src/tools/computer.ts +246 -43
- package/src/tools/skill.ts +1 -1
- package/src/tools/telegram-send.ts +6 -0
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { ArgDescriptor, CommandCtor, CommandEntry, FlagDescriptor } from "@gajae-code/utils/cli";
|
|
5
|
+
|
|
6
|
+
export type FigName = string | string[];
|
|
7
|
+
|
|
8
|
+
export interface FigArg {
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
isOptional?: boolean;
|
|
12
|
+
isVariadic?: boolean;
|
|
13
|
+
suggestions?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface FigOption {
|
|
17
|
+
name: FigName;
|
|
18
|
+
description?: string;
|
|
19
|
+
args?: FigArg;
|
|
20
|
+
isRepeatable?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FigSubcommand {
|
|
24
|
+
name: FigName;
|
|
25
|
+
description?: string;
|
|
26
|
+
options?: FigOption[];
|
|
27
|
+
args?: FigArg | FigArg[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface FigSpec extends FigSubcommand {
|
|
31
|
+
name: string;
|
|
32
|
+
subcommands?: FigSubcommand[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type InshellisenseIndexStatus = "created" | "updated" | "already-compatible" | "overwritten";
|
|
36
|
+
|
|
37
|
+
export interface InstallGjcInshellisenseSpecOptions {
|
|
38
|
+
dir?: string;
|
|
39
|
+
force?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface InstallGjcInshellisenseSpecResult {
|
|
43
|
+
directory: string;
|
|
44
|
+
specPath: string;
|
|
45
|
+
indexPath: string;
|
|
46
|
+
indexStatus: InshellisenseIndexStatus;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const GENERATED_INDEX_MARKER = "// Generated by gjc completion inshellisense";
|
|
50
|
+
const BUILTIN_ROOT_OPTIONS: FigOption[] = [
|
|
51
|
+
{ name: ["-h", "--help"], description: "Show help" },
|
|
52
|
+
{ name: ["-v", "--version"], description: "Show version" },
|
|
53
|
+
];
|
|
54
|
+
const BUILTIN_COMMAND_OPTIONS: FigOption[] = [{ name: ["-h", "--help"], description: "Show help" }];
|
|
55
|
+
|
|
56
|
+
export function defaultInshellisenseSpecDir(homeDir: string = os.homedir()): string {
|
|
57
|
+
return path.join(homeDir, ".fig", "autocomplete", "build");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function nameList(name: FigName): string[] {
|
|
61
|
+
return Array.isArray(name) ? name : [name];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function optionKey(option: FigOption): string {
|
|
65
|
+
return nameList(option.name).join("\u0000");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function combineOptions(primary: FigOption[], secondary: FigOption[]): FigOption[] {
|
|
69
|
+
const seen = new Set<string>();
|
|
70
|
+
const combined: FigOption[] = [];
|
|
71
|
+
for (const option of [...primary, ...secondary]) {
|
|
72
|
+
const key = optionKey(option);
|
|
73
|
+
if (seen.has(key)) continue;
|
|
74
|
+
seen.add(key);
|
|
75
|
+
combined.push(option);
|
|
76
|
+
}
|
|
77
|
+
return combined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function commandEntryName(entry: CommandEntry): FigName {
|
|
81
|
+
const names = [entry.name, ...(entry.aliases ?? [])];
|
|
82
|
+
return names.length === 1 ? names[0]! : names;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function flagName(name: string, descriptor: FlagDescriptor): FigName {
|
|
86
|
+
const longName = `--${name}`;
|
|
87
|
+
if (!descriptor.char) return longName;
|
|
88
|
+
return [`-${descriptor.char}`, longName];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function argSuggestions(options: readonly string[] | undefined): string[] | undefined {
|
|
92
|
+
return options && options.length > 0 ? [...options] : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function optionArgName(name: string, descriptor: FlagDescriptor): string {
|
|
96
|
+
if (descriptor.options && descriptor.options.length > 0) return name;
|
|
97
|
+
return descriptor.kind === "integer" ? "int" : "value";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function figOptionFromFlag(name: string, descriptor: FlagDescriptor): FigOption {
|
|
101
|
+
const option: FigOption = {
|
|
102
|
+
name: flagName(name, descriptor),
|
|
103
|
+
};
|
|
104
|
+
if (descriptor.description) option.description = descriptor.description;
|
|
105
|
+
if (descriptor.multiple) option.isRepeatable = true;
|
|
106
|
+
if (descriptor.kind !== "boolean") {
|
|
107
|
+
const arg: FigArg = { name: optionArgName(name, descriptor) };
|
|
108
|
+
const suggestions = argSuggestions(descriptor.options);
|
|
109
|
+
if (suggestions) arg.suggestions = suggestions;
|
|
110
|
+
option.args = arg;
|
|
111
|
+
}
|
|
112
|
+
return option;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function figOptionsFromFlags(flags: Record<string, FlagDescriptor> | undefined): FigOption[] {
|
|
116
|
+
if (!flags) return [];
|
|
117
|
+
return Object.entries(flags).map(([name, descriptor]) => figOptionFromFlag(name, descriptor));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function figArgFromDescriptor(name: string, descriptor: ArgDescriptor): FigArg {
|
|
121
|
+
const arg: FigArg = { name };
|
|
122
|
+
if (descriptor.description) arg.description = descriptor.description;
|
|
123
|
+
if (!descriptor.required) arg.isOptional = true;
|
|
124
|
+
if (descriptor.multiple) arg.isVariadic = true;
|
|
125
|
+
const suggestions = argSuggestions(descriptor.options);
|
|
126
|
+
if (suggestions) arg.suggestions = suggestions;
|
|
127
|
+
return arg;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function figArgsFromDescriptors(args: Record<string, ArgDescriptor> | undefined): FigArg | FigArg[] | undefined {
|
|
131
|
+
if (!args) return undefined;
|
|
132
|
+
const values = Object.entries(args).map(([name, descriptor]) => figArgFromDescriptor(name, descriptor));
|
|
133
|
+
if (values.length === 0) return undefined;
|
|
134
|
+
return values.length === 1 ? values[0] : values;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function commandToSubcommand(entry: CommandEntry, command: CommandCtor): FigSubcommand {
|
|
138
|
+
const subcommand: FigSubcommand = { name: commandEntryName(entry) };
|
|
139
|
+
if (command.description) subcommand.description = command.description;
|
|
140
|
+
const options = combineOptions(BUILTIN_COMMAND_OPTIONS, figOptionsFromFlags(command.flags));
|
|
141
|
+
if (options.length > 0) subcommand.options = options;
|
|
142
|
+
const args = figArgsFromDescriptors(command.args);
|
|
143
|
+
if (args) subcommand.args = args;
|
|
144
|
+
return subcommand;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function buildGjcFigSpec(entries: CommandEntry[], rootCommandOverride?: CommandCtor): Promise<FigSpec> {
|
|
148
|
+
const subcommands: FigSubcommand[] = [];
|
|
149
|
+
let rootCommand: CommandCtor | undefined = rootCommandOverride;
|
|
150
|
+
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
if (entry.name === "launch" && rootCommandOverride) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
let command: CommandCtor;
|
|
156
|
+
try {
|
|
157
|
+
command = await entry.load();
|
|
158
|
+
} catch {
|
|
159
|
+
if (entry.name !== "launch") {
|
|
160
|
+
subcommands.push({ name: commandEntryName(entry) });
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (entry.name === "launch") {
|
|
165
|
+
rootCommand = command;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (command.hidden) continue;
|
|
169
|
+
subcommands.push(commandToSubcommand(entry, command));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const spec: FigSpec = {
|
|
173
|
+
name: "gjc",
|
|
174
|
+
description: rootCommand?.description ?? "Gajae Code CLI",
|
|
175
|
+
};
|
|
176
|
+
const rootOptions = combineOptions(BUILTIN_ROOT_OPTIONS, figOptionsFromFlags(rootCommand?.flags));
|
|
177
|
+
if (rootOptions.length > 0) spec.options = rootOptions;
|
|
178
|
+
const rootArgs = figArgsFromDescriptors(rootCommand?.args);
|
|
179
|
+
if (rootArgs) spec.args = rootArgs;
|
|
180
|
+
if (subcommands.length > 0) spec.subcommands = subcommands;
|
|
181
|
+
return spec;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function renderFigSpecModule(spec: FigSpec): string {
|
|
185
|
+
return `const completionSpec = ${JSON.stringify(spec, null, "\t")};\n\nexport default completionSpec;\n`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function renderInshellisenseIndexModule(): string {
|
|
189
|
+
return `${GENERATED_INDEX_MARKER}\n\nexport default ["gjc"];\nexport const diffVersionedCompletions = [];\n`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function fileTextIfExists(filePath: string): Promise<string | undefined> {
|
|
193
|
+
try {
|
|
194
|
+
return await Bun.file(filePath).text();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined;
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function indexDeclaresGjc(content: string): boolean {
|
|
202
|
+
return /["']gjc["']/.test(content);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function resolveIndexStatus(indexPath: string, force: boolean | undefined): Promise<InshellisenseIndexStatus> {
|
|
206
|
+
const existing = await fileTextIfExists(indexPath);
|
|
207
|
+
if (existing === undefined) return "created";
|
|
208
|
+
if (existing.includes(GENERATED_INDEX_MARKER)) {
|
|
209
|
+
return existing === renderInshellisenseIndexModule() ? "already-compatible" : "updated";
|
|
210
|
+
}
|
|
211
|
+
if (indexDeclaresGjc(existing)) return "already-compatible";
|
|
212
|
+
if (force) return "overwritten";
|
|
213
|
+
throw new Error(
|
|
214
|
+
`inshellisense index already exists at ${indexPath} and does not list gjc; rerun with --force to overwrite it or pass --dir to install into a GJC-only specs directory`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function installGjcInshellisenseSpec(
|
|
219
|
+
spec: FigSpec,
|
|
220
|
+
options: InstallGjcInshellisenseSpecOptions = {},
|
|
221
|
+
): Promise<InstallGjcInshellisenseSpecResult> {
|
|
222
|
+
const directory = path.resolve(options.dir ?? defaultInshellisenseSpecDir());
|
|
223
|
+
const specPath = path.join(directory, "gjc.js");
|
|
224
|
+
const indexPath = path.join(directory, "index.js");
|
|
225
|
+
const indexStatus = await resolveIndexStatus(indexPath, options.force);
|
|
226
|
+
|
|
227
|
+
await fs.mkdir(directory, { recursive: true });
|
|
228
|
+
await Bun.write(specPath, renderFigSpecModule(spec));
|
|
229
|
+
if (indexStatus !== "already-compatible") {
|
|
230
|
+
await Bun.write(indexPath, renderInshellisenseIndexModule());
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { directory, specPath, indexPath, indexStatus };
|
|
234
|
+
}
|
package/src/cli/config-cli.ts
CHANGED
|
@@ -33,6 +33,7 @@ export interface ConfigCommandArgs {
|
|
|
33
33
|
value?: string;
|
|
34
34
|
flags: {
|
|
35
35
|
json?: boolean;
|
|
36
|
+
showSecrets?: boolean;
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
// =============================================================================
|
|
@@ -47,6 +48,19 @@ type CliSettingDef = {
|
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
const ALL_SETTING_PATHS = Object.keys(SETTINGS_SCHEMA) as SettingPath[];
|
|
51
|
+
const REDACTED_SECRET_VALUE = "<redacted>";
|
|
52
|
+
const SECRET_SETTING_SEGMENT_PATTERN = /(?:api[-_]?key|token|secret|password|passwd|pwd|credential)/i;
|
|
53
|
+
|
|
54
|
+
function isSecretSettingPath(path: string): boolean {
|
|
55
|
+
return path.split(".").some(segment => SECRET_SETTING_SEGMENT_PATTERN.test(segment));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function redactConfigValue(path: string, value: unknown, showSecrets?: boolean): unknown {
|
|
59
|
+
if (showSecrets || typeof value !== "string" || !isSecretSettingPath(path)) {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
return REDACTED_SECRET_VALUE;
|
|
63
|
+
}
|
|
50
64
|
|
|
51
65
|
/** Find setting definition by path */
|
|
52
66
|
function findSettingDef(path: string): CliSettingDef | undefined {
|
|
@@ -105,6 +119,8 @@ export function parseConfigArgs(args: string[]): ConfigCommandArgs | undefined {
|
|
|
105
119
|
const arg = args[i];
|
|
106
120
|
if (arg === "--json") {
|
|
107
121
|
result.flags.json = true;
|
|
122
|
+
} else if (arg === "--show-secrets") {
|
|
123
|
+
result.flags.showSecrets = true;
|
|
108
124
|
} else if (!arg.startsWith("-")) {
|
|
109
125
|
positionalArgs.push(arg);
|
|
110
126
|
}
|
|
@@ -265,14 +281,15 @@ export async function runConfigCommand(cmd: ConfigCommandArgs): Promise<void> {
|
|
|
265
281
|
}
|
|
266
282
|
}
|
|
267
283
|
|
|
268
|
-
function handleList(flags: { json?: boolean }): void {
|
|
284
|
+
function handleList(flags: { json?: boolean; showSecrets?: boolean }): void {
|
|
269
285
|
const defs = ALL_SETTING_PATHS.map(path => findSettingDef(path)).filter((def): def is CliSettingDef => !!def);
|
|
270
286
|
|
|
271
287
|
if (flags.json) {
|
|
272
288
|
const result: Record<string, { value: unknown; type: string; description: string }> = {};
|
|
273
289
|
for (const def of defs) {
|
|
290
|
+
const value = settings.get(def.path);
|
|
274
291
|
result[def.path] = {
|
|
275
|
-
value:
|
|
292
|
+
value: redactConfigValue(def.path, value, flags.showSecrets),
|
|
276
293
|
type: def.type,
|
|
277
294
|
description: def.description,
|
|
278
295
|
};
|
|
@@ -301,7 +318,8 @@ function handleList(flags: { json?: boolean }): void {
|
|
|
301
318
|
console.log(chalk.bold.blue(`[${group}]`));
|
|
302
319
|
for (const def of groups[group]) {
|
|
303
320
|
const value = settings.get(def.path);
|
|
304
|
-
const
|
|
321
|
+
const displayValue = redactConfigValue(def.path, value, flags.showSecrets);
|
|
322
|
+
const valueStr = formatValue(displayValue);
|
|
305
323
|
const typeStr = getTypeDisplay(def);
|
|
306
324
|
console.log(` ${chalk.white(def.path)} = ${valueStr} ${chalk.dim(typeStr)}`);
|
|
307
325
|
}
|
|
@@ -309,7 +327,7 @@ function handleList(flags: { json?: boolean }): void {
|
|
|
309
327
|
}
|
|
310
328
|
}
|
|
311
329
|
|
|
312
|
-
function handleGet(key: string | undefined, flags: { json?: boolean }): void {
|
|
330
|
+
function handleGet(key: string | undefined, flags: { json?: boolean; showSecrets?: boolean }): void {
|
|
313
331
|
if (!key) {
|
|
314
332
|
console.error(chalk.red(`Usage: ${APP_NAME} config get <key>`));
|
|
315
333
|
console.error(chalk.dim(`\nRun '${APP_NAME} config list' to see available keys`));
|
|
@@ -324,16 +342,23 @@ function handleGet(key: string | undefined, flags: { json?: boolean }): void {
|
|
|
324
342
|
}
|
|
325
343
|
|
|
326
344
|
const value = settings.get(def.path);
|
|
345
|
+
const displayValue = redactConfigValue(def.path, value, flags.showSecrets);
|
|
327
346
|
|
|
328
347
|
if (flags.json) {
|
|
329
|
-
console.log(
|
|
348
|
+
console.log(
|
|
349
|
+
JSON.stringify({ key: def.path, value: displayValue, type: def.type, description: def.description }, null, 2),
|
|
350
|
+
);
|
|
330
351
|
return;
|
|
331
352
|
}
|
|
332
353
|
|
|
333
|
-
console.log(formatValue(
|
|
354
|
+
console.log(formatValue(displayValue));
|
|
334
355
|
}
|
|
335
356
|
|
|
336
|
-
async function handleSet(
|
|
357
|
+
async function handleSet(
|
|
358
|
+
key: string | undefined,
|
|
359
|
+
value: string | undefined,
|
|
360
|
+
flags: { json?: boolean; showSecrets?: boolean },
|
|
361
|
+
): Promise<void> {
|
|
337
362
|
if (!key || value === undefined) {
|
|
338
363
|
console.error(chalk.red(`Usage: ${APP_NAME} config set <key> <value>`));
|
|
339
364
|
console.error(chalk.dim(`\nRun '${APP_NAME} config list' to see available keys`));
|
|
@@ -355,11 +380,12 @@ async function handleSet(key: string | undefined, value: string | undefined, fla
|
|
|
355
380
|
}
|
|
356
381
|
|
|
357
382
|
const newValue = settings.get(def.path);
|
|
383
|
+
const displayValue = redactConfigValue(def.path, newValue, flags.showSecrets);
|
|
358
384
|
|
|
359
385
|
if (flags.json) {
|
|
360
|
-
console.log(JSON.stringify({ key: def.path, value:
|
|
386
|
+
console.log(JSON.stringify({ key: def.path, value: displayValue }));
|
|
361
387
|
} else {
|
|
362
|
-
console.log(chalk.green(`${theme.status.success} Set ${def.path} = ${formatValue(
|
|
388
|
+
console.log(chalk.green(`${theme.status.success} Set ${def.path} = ${formatValue(displayValue)}`));
|
|
363
389
|
}
|
|
364
390
|
}
|
|
365
391
|
|
|
@@ -409,6 +435,7 @@ ${chalk.bold("Commands:")}
|
|
|
409
435
|
|
|
410
436
|
${chalk.bold("Options:")}
|
|
411
437
|
--json Output as JSON
|
|
438
|
+
--show-secrets Show secret-like setting values without redaction (unsafe)
|
|
412
439
|
|
|
413
440
|
${chalk.bold("Examples:")}
|
|
414
441
|
${APP_NAME} config list
|
|
@@ -418,6 +445,7 @@ ${chalk.bold("Examples:")}
|
|
|
418
445
|
${APP_NAME} config set defaultThinkingLevel medium
|
|
419
446
|
${APP_NAME} config reset steeringMode
|
|
420
447
|
${APP_NAME} config list --json
|
|
448
|
+
${APP_NAME} config get auth.broker.token --show-secrets
|
|
421
449
|
${APP_NAME} config init-xdg
|
|
422
450
|
|
|
423
451
|
${chalk.bold("Boolean Values:")}
|
package/src/cli/fast-help.ts
CHANGED
|
@@ -15,6 +15,7 @@ export function getExtraHelpText(): string {
|
|
|
15
15
|
${APP_NAME} deep-interview - Run requirements interview workflow
|
|
16
16
|
${APP_NAME} skills - List/read embedded workflow skills
|
|
17
17
|
${APP_NAME} config - List, get, and set configuration values
|
|
18
|
+
${APP_NAME} stats - View AI usage statistics (tokens, cost, requests)
|
|
18
19
|
${APP_NAME} notify - Send or test notifications
|
|
19
20
|
${APP_NAME} daemon - Manage background daemon helpers
|
|
20
21
|
${APP_NAME} mcp - Manage MCP server registrations
|
package/src/cli/notify-cli.ts
CHANGED
|
@@ -58,6 +58,11 @@ interface TelegramUser {
|
|
|
58
58
|
allows_users_to_create_topics?: boolean;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
interface TelegramChat {
|
|
62
|
+
id?: number | string;
|
|
63
|
+
type?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
type ThreadedModeState = "enabled" | "disabled" | "unknown";
|
|
62
67
|
type ThreadedModeFinalLabel = "verified" | "unverified" | "unknown";
|
|
63
68
|
|
|
@@ -148,6 +153,7 @@ async function runSetup(deps: NotifyCommandDeps): Promise<void> {
|
|
|
148
153
|
let chatId: string;
|
|
149
154
|
if (deps.setupChatId?.trim()) {
|
|
150
155
|
chatId = deps.setupChatId.trim();
|
|
156
|
+
await verifyPrivateChatId(fetchImpl, apiBase, token, chatId);
|
|
151
157
|
process.stdout.write(`Using provided chat id ${chatId} (non-interactive).\n`);
|
|
152
158
|
} else {
|
|
153
159
|
const stale = await getUpdates(fetchImpl, apiBase, token, { timeout: 0, allowed_updates: ["message"] });
|
|
@@ -170,16 +176,76 @@ async function runSetup(deps: NotifyCommandDeps): Promise<void> {
|
|
|
170
176
|
);
|
|
171
177
|
}
|
|
172
178
|
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
type TokenPromptInput = NodeJS.ReadStream & {
|
|
180
|
+
isRaw?: boolean;
|
|
181
|
+
setRawMode?: (mode: boolean) => unknown;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
type TokenPromptOutput = Pick<NodeJS.WriteStream, "write">;
|
|
185
|
+
|
|
186
|
+
export async function promptForToken(
|
|
187
|
+
input: TokenPromptInput = process.stdin,
|
|
188
|
+
output: TokenPromptOutput = process.stdout,
|
|
189
|
+
): Promise<string> {
|
|
190
|
+
if (!input.isTTY) {
|
|
175
191
|
throw new Error("notify setup requires an interactive TTY unless setupToken is injected.");
|
|
176
192
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return (await rl.question("Telegram BotFather token: ")).trim();
|
|
180
|
-
} finally {
|
|
181
|
-
rl.close();
|
|
193
|
+
if (typeof input.setRawMode !== "function") {
|
|
194
|
+
throw new Error("notify setup requires a TTY with raw input support unless setupToken is injected.");
|
|
182
195
|
}
|
|
196
|
+
|
|
197
|
+
output.write("Telegram BotFather token: ");
|
|
198
|
+
const wasRaw = input.isRaw === true;
|
|
199
|
+
input.setRawMode(true);
|
|
200
|
+
|
|
201
|
+
return await new Promise<string>((resolve, reject) => {
|
|
202
|
+
let value = "";
|
|
203
|
+
let settled = false;
|
|
204
|
+
|
|
205
|
+
const cleanup = () => {
|
|
206
|
+
input.off("data", onData);
|
|
207
|
+
input.off("error", onError);
|
|
208
|
+
input.setRawMode?.(wasRaw);
|
|
209
|
+
output.write("\n");
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const finish = (callback: () => void) => {
|
|
213
|
+
if (settled) return;
|
|
214
|
+
settled = true;
|
|
215
|
+
cleanup();
|
|
216
|
+
callback();
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const accept = () => finish(() => resolve(value.trim()));
|
|
220
|
+
const cancel = () => finish(() => reject(new Error("Telegram bot token prompt cancelled.")));
|
|
221
|
+
const onError = (error: Error) => finish(() => reject(error));
|
|
222
|
+
const onData = (chunk: Buffer | string) => {
|
|
223
|
+
for (const char of String(chunk)) {
|
|
224
|
+
if (char === "\r" || char === "\n") {
|
|
225
|
+
accept();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (char === "\u0003") {
|
|
229
|
+
cancel();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (char === "\u0004") {
|
|
233
|
+
if (value) accept();
|
|
234
|
+
else cancel();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (char === "\u007f" || char === "\b") {
|
|
238
|
+
value = value.slice(0, -1);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (char >= " ") value += char;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
input.on("data", onData);
|
|
246
|
+
input.once("error", onError);
|
|
247
|
+
input.resume();
|
|
248
|
+
});
|
|
183
249
|
}
|
|
184
250
|
|
|
185
251
|
const THREADED_ENABLED_SUCCESS =
|
|
@@ -223,6 +289,24 @@ async function getMe(fetchImpl: typeof fetch, apiBase: string, token: string): P
|
|
|
223
289
|
return user;
|
|
224
290
|
}
|
|
225
291
|
|
|
292
|
+
async function verifyPrivateChatId(
|
|
293
|
+
fetchImpl: typeof fetch,
|
|
294
|
+
apiBase: string,
|
|
295
|
+
token: string,
|
|
296
|
+
chatId: string,
|
|
297
|
+
): Promise<void> {
|
|
298
|
+
const chat = (await callTelegram<unknown>(fetchImpl, apiBase, token, "getChat", { chat_id: chatId })) as
|
|
299
|
+
| TelegramChat
|
|
300
|
+
| undefined;
|
|
301
|
+
if (!chat || typeof chat !== "object") {
|
|
302
|
+
throw new Error("Telegram getChat returned invalid Telegram response: missing valid Chat result.");
|
|
303
|
+
}
|
|
304
|
+
if (chat.type !== "private") {
|
|
305
|
+
const type = typeof chat.type === "string" && chat.type ? chat.type : "unknown";
|
|
306
|
+
throw new Error(`Provided chat id ${chatId} is a ${type} chat; pairing requires a private Telegram chat.`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
226
310
|
function threadedModeState(user: TelegramUser): ThreadedModeState {
|
|
227
311
|
if (user.has_topics_enabled === true) return "enabled";
|
|
228
312
|
if (user.has_topics_enabled === false) return "disabled";
|
package/src/cli.ts
CHANGED
|
@@ -36,6 +36,7 @@ export const commands: CommandEntry[] = [
|
|
|
36
36
|
{ name: "gc", load: () => import("./commands/gc").then(m => m.default) },
|
|
37
37
|
{ name: "ralplan", load: () => import("./commands/ralplan").then(m => m.default) },
|
|
38
38
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
39
|
+
{ name: "stats", load: () => import("./commands/stats").then(m => m.default) },
|
|
39
40
|
{ name: "notify", load: () => import("./commands/notify").then(m => m.default) },
|
|
40
41
|
{ name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
|
|
41
42
|
{ name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
|
|
@@ -52,6 +53,7 @@ export const commands: CommandEntry[] = [
|
|
|
52
53
|
{ name: "rlm", load: () => import("./commands/rlm").then(m => m.default) },
|
|
53
54
|
{ name: "update", load: () => import("./commands/update").then(m => m.default) },
|
|
54
55
|
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
56
|
+
{ name: "completion", load: () => import("./commands/completion").then(m => m.default) },
|
|
55
57
|
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
|
|
56
58
|
];
|
|
57
59
|
|
|
@@ -110,7 +112,7 @@ function hasRootVersionFlag(argv: string[]): boolean {
|
|
|
110
112
|
return hasRootFastFlag(argv, versionFlags);
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
class RootHelpCommand extends Command {
|
|
115
|
+
export class RootHelpCommand extends Command {
|
|
114
116
|
static description = "Red-claw AI coding assistant";
|
|
115
117
|
static hidden = true;
|
|
116
118
|
static args = {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate shell completion specs for external completion engines.
|
|
3
|
+
*/
|
|
4
|
+
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
5
|
+
import { commands, RootHelpCommand } from "../cli";
|
|
6
|
+
import {
|
|
7
|
+
buildGjcFigSpec,
|
|
8
|
+
defaultInshellisenseSpecDir,
|
|
9
|
+
type InstallGjcInshellisenseSpecResult,
|
|
10
|
+
installGjcInshellisenseSpec,
|
|
11
|
+
renderFigSpecModule,
|
|
12
|
+
} from "../cli/completion-cli";
|
|
13
|
+
|
|
14
|
+
const TARGETS = ["inshellisense", "fig"] as const;
|
|
15
|
+
|
|
16
|
+
type CompletionTarget = (typeof TARGETS)[number];
|
|
17
|
+
|
|
18
|
+
function normalizeTarget(target: CompletionTarget): "inshellisense" {
|
|
19
|
+
return target === "fig" ? "inshellisense" : target;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatInstallResult(result: InstallGjcInshellisenseSpecResult, customDir: boolean): string {
|
|
23
|
+
const lines = [
|
|
24
|
+
"Installed GJC inshellisense completion spec:",
|
|
25
|
+
` spec: ${result.specPath}`,
|
|
26
|
+
` index: ${result.indexPath} (${result.indexStatus})`,
|
|
27
|
+
];
|
|
28
|
+
if (customDir) {
|
|
29
|
+
lines.push(
|
|
30
|
+
"Add the install directory to inshellisense's [specs].path setting, then restart the inshellisense session.",
|
|
31
|
+
);
|
|
32
|
+
} else {
|
|
33
|
+
lines.push("Restart the inshellisense session, or run `is reinit` if your shell integration needs a refresh.");
|
|
34
|
+
}
|
|
35
|
+
return `${lines.join("\n")}\n`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default class Completion extends Command {
|
|
39
|
+
static description = "Generate shell completion specs";
|
|
40
|
+
|
|
41
|
+
static args = {
|
|
42
|
+
target: Args.string({
|
|
43
|
+
description: "Completion format",
|
|
44
|
+
required: false,
|
|
45
|
+
options: TARGETS,
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
static flags = {
|
|
50
|
+
install: Flags.boolean({ description: "Install the generated spec for inshellisense" }),
|
|
51
|
+
dir: Flags.string({ description: "Install directory (defaults to ~/.fig/autocomplete/build)" }),
|
|
52
|
+
force: Flags.boolean({ description: "Overwrite an existing non-GJC inshellisense index.js" }),
|
|
53
|
+
json: Flags.boolean({ description: "Output JSON instead of JavaScript or status text" }),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
async run(): Promise<void> {
|
|
57
|
+
const { args, flags } = await this.parse(Completion);
|
|
58
|
+
const target = normalizeTarget((args.target ?? "inshellisense") as CompletionTarget);
|
|
59
|
+
if (target !== "inshellisense") {
|
|
60
|
+
process.stderr.write(`Unsupported completion target: ${target}\n`);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if ((flags.dir || flags.force) && !flags.install) {
|
|
65
|
+
process.stderr.write("--dir and --force only apply with --install\n");
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const spec = await buildGjcFigSpec(commands, RootHelpCommand);
|
|
71
|
+
if (!flags.install) {
|
|
72
|
+
process.stdout.write(flags.json ? `${JSON.stringify(spec, null, 2)}\n` : renderFigSpecModule(spec));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const directory = flags.dir ?? defaultInshellisenseSpecDir();
|
|
77
|
+
const result = await installGjcInshellisenseSpec(spec, { dir: directory, force: flags.force });
|
|
78
|
+
if (flags.json) {
|
|
79
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
process.stdout.write(formatInstallResult(result, flags.dir !== undefined));
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/commands/config.ts
CHANGED
|
@@ -29,6 +29,7 @@ export default class Config extends Command {
|
|
|
29
29
|
|
|
30
30
|
static flags = {
|
|
31
31
|
json: Flags.boolean({ description: "Output JSON" }),
|
|
32
|
+
"show-secrets": Flags.boolean({ description: "Show secret-like setting values without redaction (unsafe)" }),
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
async run(): Promise<void> {
|
|
@@ -42,6 +43,7 @@ export default class Config extends Command {
|
|
|
42
43
|
value,
|
|
43
44
|
flags: {
|
|
44
45
|
json: flags.json,
|
|
46
|
+
showSecrets: flags["show-secrets"],
|
|
45
47
|
},
|
|
46
48
|
};
|
|
47
49
|
|
|
@@ -8,6 +8,7 @@ export default class DeepInterview extends Command {
|
|
|
8
8
|
quick: Flags.boolean({ description: "Seed a quick deep-interview run" }),
|
|
9
9
|
standard: Flags.boolean({ description: "Seed a standard deep-interview run" }),
|
|
10
10
|
deep: Flags.boolean({ description: "Seed a deep deep-interview run" }),
|
|
11
|
+
trace: Flags.boolean({ description: "Run a bounded trace evidence pre-step before interview questions" }),
|
|
11
12
|
threshold: Flags.string({ description: "Override ambiguity threshold for kickoff" }),
|
|
12
13
|
"threshold-source": Flags.string({ description: "Describe the threshold override source" }),
|
|
13
14
|
"session-id": Flags.string({
|
|
@@ -25,7 +26,7 @@ export default class DeepInterview extends Command {
|
|
|
25
26
|
json: Flags.boolean({ description: "Output JSON" }),
|
|
26
27
|
};
|
|
27
28
|
static examples = [
|
|
28
|
-
'$ gjc deep-interview --standard "<idea>"',
|
|
29
|
+
'$ gjc deep-interview --trace --standard "<idea>"',
|
|
29
30
|
"$ gjc deep-interview --write --stage final --slug my-feature --spec ./final-spec.md",
|
|
30
31
|
"$ gjc deep-interview --write --stage final --slug my-feature --spec ./final-spec.md --deliberate",
|
|
31
32
|
];
|