sdd-cli 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -688
- package/dist/cli.js +108 -8
- package/dist/commands/ai-autopilot.d.ts +19 -0
- package/dist/commands/ai-autopilot.js +1292 -0
- package/dist/commands/ai-exec.js +14 -3
- package/dist/commands/ai-status.js +17 -5
- package/dist/commands/app-lifecycle.d.ts +25 -0
- package/dist/commands/app-lifecycle.js +505 -0
- package/dist/commands/hello.js +53 -1
- package/dist/commands/suite.d.ts +1 -0
- package/dist/commands/suite.js +82 -0
- package/dist/config/index.d.ts +23 -0
- package/dist/config/index.js +209 -0
- package/dist/context/flags.d.ts +2 -0
- package/dist/context/flags.js +9 -1
- package/dist/providers/codex.d.ts +3 -5
- package/dist/providers/codex.js +34 -2
- package/dist/providers/gemini.d.ts +5 -0
- package/dist/providers/gemini.js +82 -0
- package/dist/providers/index.d.ts +16 -0
- package/dist/providers/index.js +90 -0
- package/dist/providers/types.d.ts +13 -0
- package/dist/providers/types.js +2 -0
- package/dist/router/intent.js +34 -4
- package/dist/workspace/index.js +6 -6
- package/package.json +4 -3
package/dist/commands/hello.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.runHello = runHello;
|
|
4
7
|
const intent_1 = require("../router/intent");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
5
9
|
const index_1 = require("../workspace/index");
|
|
6
10
|
const prompt_1 = require("../ui/prompt");
|
|
7
11
|
const prompt_packs_1 = require("../router/prompt-packs");
|
|
@@ -15,6 +19,8 @@ const route_1 = require("./route");
|
|
|
15
19
|
const test_plan_1 = require("./test-plan");
|
|
16
20
|
const local_metrics_1 = require("../telemetry/local-metrics");
|
|
17
21
|
const errors_1 = require("../errors");
|
|
22
|
+
const ai_autopilot_1 = require("./ai-autopilot");
|
|
23
|
+
const app_lifecycle_1 = require("./app-lifecycle");
|
|
18
24
|
const autopilot_checkpoint_1 = require("./autopilot-checkpoint");
|
|
19
25
|
function printStep(step, description) {
|
|
20
26
|
console.log(`${step}: ${description}`);
|
|
@@ -120,6 +126,7 @@ async function runHello(input, runQuestions) {
|
|
|
120
126
|
const autoGuidedMode = !shouldRunQuestions && (runtimeFlags.nonInteractive || hasDirectIntent);
|
|
121
127
|
const dryRun = runtimeFlags.dryRun;
|
|
122
128
|
const beginnerMode = runtimeFlags.beginner;
|
|
129
|
+
const provider = runtimeFlags.provider;
|
|
123
130
|
console.log("Hello from sdd-cli.");
|
|
124
131
|
console.log(`Workspace: ${workspace.root}`);
|
|
125
132
|
if (beginnerMode) {
|
|
@@ -127,6 +134,7 @@ async function runHello(input, runQuestions) {
|
|
|
127
134
|
}
|
|
128
135
|
if (autoGuidedMode) {
|
|
129
136
|
printWhy("Auto-guided mode active: using current workspace defaults.");
|
|
137
|
+
printWhy(`AI provider preference: ${provider ?? "gemini"}`);
|
|
130
138
|
}
|
|
131
139
|
else {
|
|
132
140
|
const useWorkspace = await (0, prompt_1.confirm)("Use this workspace path? (y/n) ");
|
|
@@ -288,7 +296,7 @@ async function runHello(input, runQuestions) {
|
|
|
288
296
|
fromStep = candidate;
|
|
289
297
|
}
|
|
290
298
|
}
|
|
291
|
-
const draft = buildAutopilotDraft(text, intent.flow, intent.domain);
|
|
299
|
+
const draft = (0, ai_autopilot_1.enrichDraftWithAI)(text, intent.flow, intent.domain, buildAutopilotDraft(text, intent.flow, intent.domain), provider);
|
|
292
300
|
draft.project_name = activeProject;
|
|
293
301
|
let reqId = checkpoint?.reqId ?? "";
|
|
294
302
|
const startStep = fromStep ?? "create";
|
|
@@ -389,6 +397,50 @@ async function runHello(input, runQuestions) {
|
|
|
389
397
|
return;
|
|
390
398
|
}
|
|
391
399
|
(0, autopilot_checkpoint_1.clearCheckpoint)(activeProject);
|
|
400
|
+
const projectRoot = path_1.default.resolve(finished.doneDir, "..", "..", "..");
|
|
401
|
+
const codeBootstrap = (0, ai_autopilot_1.bootstrapProjectCode)(projectRoot, activeProject, text, provider);
|
|
402
|
+
if (!codeBootstrap.generated) {
|
|
403
|
+
printWhy(`Code generation blocked: ${codeBootstrap.reason || "provider did not return valid files"}.`);
|
|
404
|
+
printWhy("No template fallback was applied. Re-run with clearer prompt or improve provider response contract.");
|
|
405
|
+
printRecoveryNext(activeProject, "finish", text);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
printWhy(`Code scaffold ready at: ${codeBootstrap.outputDir} (${codeBootstrap.fileCount} files)`);
|
|
409
|
+
if (codeBootstrap.reason) {
|
|
410
|
+
printWhy(`Code scaffold note: ${codeBootstrap.reason}`);
|
|
411
|
+
}
|
|
412
|
+
let lifecycle = (0, app_lifecycle_1.runAppLifecycle)(projectRoot, activeProject, {
|
|
413
|
+
goalText: text,
|
|
414
|
+
intentSignals: intent.signals
|
|
415
|
+
});
|
|
416
|
+
lifecycle.summary.forEach((line) => printWhy(`Lifecycle: ${line}`));
|
|
417
|
+
const lifecycleDisabled = process.env.SDD_DISABLE_APP_LIFECYCLE === "1";
|
|
418
|
+
if (!lifecycleDisabled && !lifecycle.qualityPassed) {
|
|
419
|
+
const appDir = path_1.default.join(projectRoot, "generated-app");
|
|
420
|
+
const parsedAttempts = Number.parseInt(process.env.SDD_AI_REPAIR_MAX_ATTEMPTS ?? "", 10);
|
|
421
|
+
const maxRepairAttempts = Number.isFinite(parsedAttempts) && parsedAttempts > 0 ? parsedAttempts : 6;
|
|
422
|
+
printWhy("Quality gates failed. Attempting AI repair iterations.");
|
|
423
|
+
lifecycle.qualityDiagnostics.forEach((issue) => printWhy(`Quality issue: ${issue}`));
|
|
424
|
+
for (let attempt = 1; attempt <= maxRepairAttempts && !lifecycle.qualityPassed; attempt += 1) {
|
|
425
|
+
const repair = (0, ai_autopilot_1.improveGeneratedApp)(appDir, text, provider, lifecycle.qualityDiagnostics);
|
|
426
|
+
if (repair.attempted && repair.applied) {
|
|
427
|
+
printWhy(`AI repair attempt ${attempt} applied (${repair.fileCount} files). Re-running lifecycle checks.`);
|
|
428
|
+
lifecycle = (0, app_lifecycle_1.runAppLifecycle)(projectRoot, activeProject, {
|
|
429
|
+
goalText: text,
|
|
430
|
+
intentSignals: intent.signals
|
|
431
|
+
});
|
|
432
|
+
lifecycle.summary.forEach((line) => printWhy(`Lifecycle (retry ${attempt}): ${line}`));
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
printWhy(`AI repair attempt ${attempt} skipped: ${repair.reason || "unknown reason"}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (!lifecycle.qualityPassed) {
|
|
439
|
+
printWhy("Quality still failing after AI repair attempts. Stopping without template fallback.");
|
|
440
|
+
printRecoveryNext(activeProject, "finish", text);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
392
444
|
(0, local_metrics_1.recordActivationMetric)("completed", {
|
|
393
445
|
project: activeProject,
|
|
394
446
|
reqId
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSuite(initialInput?: string): Promise<void>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSuite = runSuite;
|
|
4
|
+
const prompt_1 = require("../ui/prompt");
|
|
5
|
+
const flags_1 = require("../context/flags");
|
|
6
|
+
const hello_1 = require("./hello");
|
|
7
|
+
function inferAppType(text) {
|
|
8
|
+
const lower = text.toLowerCase();
|
|
9
|
+
if (/\bdesktop\b|\bwindows\b|\belectron\b/.test(lower)) {
|
|
10
|
+
return "desktop";
|
|
11
|
+
}
|
|
12
|
+
if (/\bweb\b|\bbrowser\b|\bsite\b|\bfrontend\b/.test(lower)) {
|
|
13
|
+
return "web";
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
function inferStack(text) {
|
|
18
|
+
const lower = text.toLowerCase();
|
|
19
|
+
if (/\btypescript\b|\bts\b/.test(lower)) {
|
|
20
|
+
return "typescript";
|
|
21
|
+
}
|
|
22
|
+
if (/\bjavascript\b|\bjs\b/.test(lower)) {
|
|
23
|
+
return "javascript";
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
async function resolveBlockers(input) {
|
|
28
|
+
const flags = (0, flags_1.getFlags)();
|
|
29
|
+
const nonInteractive = flags.nonInteractive;
|
|
30
|
+
const inferredType = inferAppType(input);
|
|
31
|
+
const inferredStack = inferStack(input);
|
|
32
|
+
let appType = inferredType;
|
|
33
|
+
let stack = inferredStack;
|
|
34
|
+
if (!appType) {
|
|
35
|
+
if (nonInteractive) {
|
|
36
|
+
appType = "web";
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const answer = (await (0, prompt_1.ask)("Blocker: app type? (web/desktop) ")).trim().toLowerCase();
|
|
40
|
+
appType = answer === "desktop" ? "desktop" : "web";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!stack) {
|
|
44
|
+
if (nonInteractive) {
|
|
45
|
+
stack = "javascript";
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const answer = (await (0, prompt_1.ask)("Blocker: stack? (javascript/typescript) ")).trim().toLowerCase();
|
|
49
|
+
stack = answer === "typescript" ? "typescript" : "javascript";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { appType, stack };
|
|
53
|
+
}
|
|
54
|
+
function enrichIntent(intent, context) {
|
|
55
|
+
return `${intent}. Build target: ${context.appType}. Preferred stack: ${context.stack}. Finish complete delivery including tests and deployment notes.`;
|
|
56
|
+
}
|
|
57
|
+
async function runSuite(initialInput) {
|
|
58
|
+
const startedNonInteractive = (0, flags_1.getFlags)().nonInteractive;
|
|
59
|
+
console.log("SDD Suite started. Type 'exit' to close.");
|
|
60
|
+
let current = (initialInput ?? "").trim();
|
|
61
|
+
while (true) {
|
|
62
|
+
if (!current) {
|
|
63
|
+
if (startedNonInteractive) {
|
|
64
|
+
console.log("Suite finished.");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
current = (await (0, prompt_1.ask)("suite> ")).trim();
|
|
68
|
+
}
|
|
69
|
+
if (!current) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (current.toLowerCase() === "exit" || current.toLowerCase() === "quit") {
|
|
73
|
+
console.log("Suite finished.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const context = await resolveBlockers(current);
|
|
77
|
+
const enriched = enrichIntent(current, context);
|
|
78
|
+
await (0, hello_1.runHello)(enriched, false);
|
|
79
|
+
console.log("Suite task completed. Enter next instruction or 'exit'.");
|
|
80
|
+
current = "";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type SddModeDefault = "guided" | "non-interactive" | "beginner";
|
|
2
|
+
export type SddProviderDefault = "gemini" | "codex" | "auto";
|
|
3
|
+
export type SddConfig = {
|
|
4
|
+
workspace: {
|
|
5
|
+
default_root: string;
|
|
6
|
+
};
|
|
7
|
+
ai: {
|
|
8
|
+
preferred_cli: SddProviderDefault;
|
|
9
|
+
model: string;
|
|
10
|
+
};
|
|
11
|
+
mode: {
|
|
12
|
+
default: SddModeDefault;
|
|
13
|
+
};
|
|
14
|
+
git: {
|
|
15
|
+
publish_enabled: boolean;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare function configPath(): string;
|
|
19
|
+
export declare function defaultConfig(): SddConfig;
|
|
20
|
+
export declare function loadConfig(): SddConfig;
|
|
21
|
+
export declare function saveConfig(config: SddConfig): string;
|
|
22
|
+
export declare function ensureConfig(): SddConfig;
|
|
23
|
+
export declare function updateConfigValue(key: string, value: string): SddConfig | null;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.configPath = configPath;
|
|
7
|
+
exports.defaultConfig = defaultConfig;
|
|
8
|
+
exports.loadConfig = loadConfig;
|
|
9
|
+
exports.saveConfig = saveConfig;
|
|
10
|
+
exports.ensureConfig = ensureConfig;
|
|
11
|
+
exports.updateConfigValue = updateConfigValue;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const os_1 = __importDefault(require("os"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
function homeDocumentsDir() {
|
|
16
|
+
const home = os_1.default.homedir();
|
|
17
|
+
return path_1.default.join(home, "Documents");
|
|
18
|
+
}
|
|
19
|
+
function inferUserName(home) {
|
|
20
|
+
const normalized = home.replace(/\\/g, "/").split("/").filter((part) => part.length > 0);
|
|
21
|
+
return normalized.length > 0 ? normalized[normalized.length - 1] : "user";
|
|
22
|
+
}
|
|
23
|
+
function configPath() {
|
|
24
|
+
const override = process.env.SDD_CONFIG_PATH?.trim();
|
|
25
|
+
if (override) {
|
|
26
|
+
return path_1.default.resolve(override);
|
|
27
|
+
}
|
|
28
|
+
const root = process.env.APPDATA
|
|
29
|
+
? path_1.default.join(process.env.APPDATA, "sdd-cli")
|
|
30
|
+
: path_1.default.join(os_1.default.homedir(), ".config", "sdd-cli");
|
|
31
|
+
return path_1.default.join(root, "config.yml");
|
|
32
|
+
}
|
|
33
|
+
function defaultConfig() {
|
|
34
|
+
return {
|
|
35
|
+
workspace: {
|
|
36
|
+
default_root: path_1.default.join(homeDocumentsDir(), "sdd-tool-projects")
|
|
37
|
+
},
|
|
38
|
+
ai: {
|
|
39
|
+
preferred_cli: "gemini",
|
|
40
|
+
model: "gemini-2.5-flash-lite"
|
|
41
|
+
},
|
|
42
|
+
mode: {
|
|
43
|
+
default: "guided"
|
|
44
|
+
},
|
|
45
|
+
git: {
|
|
46
|
+
publish_enabled: false
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function normalizeProvider(value) {
|
|
51
|
+
const clean = value.trim().toLowerCase();
|
|
52
|
+
if (clean === "codex" || clean === "auto" || clean === "gemini") {
|
|
53
|
+
return clean;
|
|
54
|
+
}
|
|
55
|
+
return "gemini";
|
|
56
|
+
}
|
|
57
|
+
function normalizeMode(value) {
|
|
58
|
+
const clean = value.trim().toLowerCase();
|
|
59
|
+
if (clean === "non-interactive") {
|
|
60
|
+
return "non-interactive";
|
|
61
|
+
}
|
|
62
|
+
if (clean === "beginner") {
|
|
63
|
+
return "beginner";
|
|
64
|
+
}
|
|
65
|
+
return "guided";
|
|
66
|
+
}
|
|
67
|
+
function expandRoot(value) {
|
|
68
|
+
let out = value.trim();
|
|
69
|
+
const home = os_1.default.homedir();
|
|
70
|
+
const user = inferUserName(home);
|
|
71
|
+
out = out.replace(/\{\{user\}\}/gi, user);
|
|
72
|
+
out = out.replace(/\{\{home\}\}/gi, home);
|
|
73
|
+
if (out.startsWith("~/")) {
|
|
74
|
+
out = path_1.default.join(home, out.slice(2));
|
|
75
|
+
}
|
|
76
|
+
return path_1.default.resolve(out);
|
|
77
|
+
}
|
|
78
|
+
function parseSimpleYaml(raw) {
|
|
79
|
+
const result = {};
|
|
80
|
+
let section = "";
|
|
81
|
+
const lines = raw.split(/\r?\n/);
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
const trimmed = line.trim();
|
|
84
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const sectionMatch = /^([a-zA-Z_][a-zA-Z0-9_-]*):\s*$/.exec(trimmed);
|
|
88
|
+
if (sectionMatch) {
|
|
89
|
+
section = sectionMatch[1];
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const valueMatch = /^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.+)\s*$/.exec(trimmed);
|
|
93
|
+
if (!valueMatch || !section) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const key = valueMatch[1];
|
|
97
|
+
const value = valueMatch[2].replace(/^["']|["']$/g, "");
|
|
98
|
+
if (section === "workspace" && key === "default_root") {
|
|
99
|
+
result.workspace = { default_root: value };
|
|
100
|
+
}
|
|
101
|
+
else if (section === "ai" && key === "preferred_cli") {
|
|
102
|
+
result.ai = { ...(result.ai ?? { model: defaultConfig().ai.model }), preferred_cli: normalizeProvider(value) };
|
|
103
|
+
}
|
|
104
|
+
else if (section === "ai" && key === "model") {
|
|
105
|
+
result.ai = { ...(result.ai ?? { preferred_cli: defaultConfig().ai.preferred_cli }), model: value.trim() };
|
|
106
|
+
}
|
|
107
|
+
else if (section === "mode" && key === "default") {
|
|
108
|
+
result.mode = { default: normalizeMode(value) };
|
|
109
|
+
}
|
|
110
|
+
else if (section === "git" && key === "publish_enabled") {
|
|
111
|
+
result.git = { publish_enabled: value.trim().toLowerCase() === "true" };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
function renderYaml(config) {
|
|
117
|
+
return [
|
|
118
|
+
"# sdd-cli configuration",
|
|
119
|
+
"# You can use {{user}} or {{home}} in workspace.default_root",
|
|
120
|
+
"workspace:",
|
|
121
|
+
` default_root: ${config.workspace.default_root}`,
|
|
122
|
+
"ai:",
|
|
123
|
+
` preferred_cli: ${config.ai.preferred_cli}`,
|
|
124
|
+
` model: ${config.ai.model}`,
|
|
125
|
+
"mode:",
|
|
126
|
+
` default: ${config.mode.default}`,
|
|
127
|
+
"git:",
|
|
128
|
+
` publish_enabled: ${config.git.publish_enabled ? "true" : "false"}`,
|
|
129
|
+
""
|
|
130
|
+
].join("\n");
|
|
131
|
+
}
|
|
132
|
+
function mergeConfig(base, input) {
|
|
133
|
+
const root = input.workspace?.default_root ? expandRoot(input.workspace.default_root) : base.workspace.default_root;
|
|
134
|
+
return {
|
|
135
|
+
workspace: {
|
|
136
|
+
default_root: root
|
|
137
|
+
},
|
|
138
|
+
ai: {
|
|
139
|
+
preferred_cli: input.ai?.preferred_cli ? normalizeProvider(input.ai.preferred_cli) : base.ai.preferred_cli,
|
|
140
|
+
model: typeof input.ai?.model === "string" && input.ai.model.trim() ? input.ai.model.trim() : base.ai.model
|
|
141
|
+
},
|
|
142
|
+
mode: {
|
|
143
|
+
default: input.mode?.default ? normalizeMode(input.mode.default) : base.mode.default
|
|
144
|
+
},
|
|
145
|
+
git: {
|
|
146
|
+
publish_enabled: typeof input.git?.publish_enabled === "boolean" ? input.git.publish_enabled : base.git.publish_enabled
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function loadConfig() {
|
|
151
|
+
const defaults = defaultConfig();
|
|
152
|
+
const file = configPath();
|
|
153
|
+
if (!fs_1.default.existsSync(file)) {
|
|
154
|
+
return defaults;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const raw = fs_1.default.readFileSync(file, "utf-8");
|
|
158
|
+
return mergeConfig(defaults, parseSimpleYaml(raw));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return defaults;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function saveConfig(config) {
|
|
165
|
+
const file = configPath();
|
|
166
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
167
|
+
fs_1.default.writeFileSync(file, renderYaml(config), "utf-8");
|
|
168
|
+
return file;
|
|
169
|
+
}
|
|
170
|
+
function ensureConfig() {
|
|
171
|
+
const existing = loadConfig();
|
|
172
|
+
const file = configPath();
|
|
173
|
+
if (!fs_1.default.existsSync(file)) {
|
|
174
|
+
saveConfig(existing);
|
|
175
|
+
}
|
|
176
|
+
fs_1.default.mkdirSync(existing.workspace.default_root, { recursive: true });
|
|
177
|
+
return existing;
|
|
178
|
+
}
|
|
179
|
+
function updateConfigValue(key, value) {
|
|
180
|
+
const current = ensureConfig();
|
|
181
|
+
const next = {
|
|
182
|
+
workspace: { ...current.workspace },
|
|
183
|
+
ai: { ...current.ai },
|
|
184
|
+
mode: { ...current.mode },
|
|
185
|
+
git: { ...current.git }
|
|
186
|
+
};
|
|
187
|
+
const normalized = key.trim().toLowerCase();
|
|
188
|
+
if (normalized === "workspace.default_root") {
|
|
189
|
+
next.workspace.default_root = expandRoot(value);
|
|
190
|
+
}
|
|
191
|
+
else if (normalized === "ai.preferred_cli") {
|
|
192
|
+
next.ai.preferred_cli = normalizeProvider(value);
|
|
193
|
+
}
|
|
194
|
+
else if (normalized === "ai.model") {
|
|
195
|
+
next.ai.model = value.trim() || next.ai.model;
|
|
196
|
+
}
|
|
197
|
+
else if (normalized === "mode.default") {
|
|
198
|
+
next.mode.default = normalizeMode(value);
|
|
199
|
+
}
|
|
200
|
+
else if (normalized === "git.publish_enabled") {
|
|
201
|
+
next.git.publish_enabled = value.trim().toLowerCase() === "true";
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
saveConfig(next);
|
|
207
|
+
fs_1.default.mkdirSync(next.workspace.default_root, { recursive: true });
|
|
208
|
+
return next;
|
|
209
|
+
}
|
package/dist/context/flags.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type RuntimeFlags = {
|
|
|
10
10
|
output?: string;
|
|
11
11
|
scope?: string;
|
|
12
12
|
metricsLocal?: boolean;
|
|
13
|
+
provider?: string;
|
|
14
|
+
model?: string;
|
|
13
15
|
};
|
|
14
16
|
export declare function setFlags(next: Partial<RuntimeFlags>): void;
|
|
15
17
|
export declare function getFlags(): RuntimeFlags;
|
package/dist/context/flags.js
CHANGED
|
@@ -13,7 +13,9 @@ const flags = {
|
|
|
13
13
|
project: undefined,
|
|
14
14
|
output: undefined,
|
|
15
15
|
scope: undefined,
|
|
16
|
-
metricsLocal: false
|
|
16
|
+
metricsLocal: false,
|
|
17
|
+
provider: process.env.SDD_AI_PROVIDER_DEFAULT ?? "gemini",
|
|
18
|
+
model: process.env.SDD_AI_MODEL_DEFAULT
|
|
17
19
|
};
|
|
18
20
|
function setFlags(next) {
|
|
19
21
|
if ("approve" in next) {
|
|
@@ -49,6 +51,12 @@ function setFlags(next) {
|
|
|
49
51
|
if ("metricsLocal" in next) {
|
|
50
52
|
flags.metricsLocal = Boolean(next.metricsLocal);
|
|
51
53
|
}
|
|
54
|
+
if ("provider" in next) {
|
|
55
|
+
flags.provider = typeof next.provider === "string" ? next.provider : flags.provider;
|
|
56
|
+
}
|
|
57
|
+
if ("model" in next) {
|
|
58
|
+
flags.model = typeof next.model === "string" ? next.model : flags.model;
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
61
|
function getFlags() {
|
|
54
62
|
return { ...flags };
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
output: string;
|
|
4
|
-
error?: string;
|
|
5
|
-
};
|
|
1
|
+
import { AIProvider, ProviderResult } from "./types";
|
|
2
|
+
export type CodexResult = ProviderResult;
|
|
6
3
|
export declare function codexVersion(): CodexResult;
|
|
7
4
|
export declare function codexExec(prompt: string): CodexResult;
|
|
5
|
+
export declare const codexProvider: AIProvider;
|
package/dist/providers/codex.js
CHANGED
|
@@ -1,19 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codexProvider = void 0;
|
|
3
4
|
exports.codexVersion = codexVersion;
|
|
4
5
|
exports.codexExec = codexExec;
|
|
5
6
|
const child_process_1 = require("child_process");
|
|
7
|
+
function resolveCommand(input) {
|
|
8
|
+
if (process.platform !== "win32") {
|
|
9
|
+
return input;
|
|
10
|
+
}
|
|
11
|
+
const looksLikePath = input.includes("\\") || input.includes("/");
|
|
12
|
+
const hasExt = /\.[A-Za-z0-9]+$/.test(input);
|
|
13
|
+
if (!looksLikePath && !hasExt) {
|
|
14
|
+
return `${input}.cmd`;
|
|
15
|
+
}
|
|
16
|
+
return input;
|
|
17
|
+
}
|
|
6
18
|
function codexVersion() {
|
|
7
|
-
const
|
|
19
|
+
const command = resolveCommand(process.env.SDD_CODEX_BIN?.trim() || "codex");
|
|
20
|
+
const useShell = process.platform === "win32" && command.toLowerCase().endsWith(".cmd");
|
|
21
|
+
const result = (0, child_process_1.spawnSync)(command, ["--version"], {
|
|
22
|
+
encoding: "utf-8",
|
|
23
|
+
shell: useShell
|
|
24
|
+
});
|
|
8
25
|
if (result.status !== 0) {
|
|
9
26
|
return { ok: false, output: "", error: result.stderr || "codex not available" };
|
|
10
27
|
}
|
|
11
28
|
return { ok: true, output: result.stdout.trim() };
|
|
12
29
|
}
|
|
13
30
|
function codexExec(prompt) {
|
|
14
|
-
const
|
|
31
|
+
const command = resolveCommand(process.env.SDD_CODEX_BIN?.trim() || "codex");
|
|
32
|
+
const useShell = process.platform === "win32" && command.toLowerCase().endsWith(".cmd");
|
|
33
|
+
const result = useShell
|
|
34
|
+
? (0, child_process_1.spawnSync)(`${command} exec "${prompt.replace(/"/g, "\"\"")}"`, {
|
|
35
|
+
encoding: "utf-8",
|
|
36
|
+
shell: true
|
|
37
|
+
})
|
|
38
|
+
: (0, child_process_1.spawnSync)(command, ["exec", prompt], {
|
|
39
|
+
encoding: "utf-8"
|
|
40
|
+
});
|
|
15
41
|
if (result.status !== 0) {
|
|
16
42
|
return { ok: false, output: result.stdout || "", error: result.stderr || "codex exec failed" };
|
|
17
43
|
}
|
|
18
44
|
return { ok: true, output: result.stdout.trim() };
|
|
19
45
|
}
|
|
46
|
+
exports.codexProvider = {
|
|
47
|
+
id: "codex",
|
|
48
|
+
label: "Codex",
|
|
49
|
+
version: codexVersion,
|
|
50
|
+
exec: codexExec
|
|
51
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AIProvider, ProviderResult } from "./types";
|
|
2
|
+
export type GeminiResult = ProviderResult;
|
|
3
|
+
export declare function geminiVersion(): GeminiResult;
|
|
4
|
+
export declare function geminiExec(prompt: string): GeminiResult;
|
|
5
|
+
export declare const geminiProvider: AIProvider;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.geminiProvider = void 0;
|
|
4
|
+
exports.geminiVersion = geminiVersion;
|
|
5
|
+
exports.geminiExec = geminiExec;
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
function resolveCommand(input) {
|
|
8
|
+
if (process.platform !== "win32") {
|
|
9
|
+
return input;
|
|
10
|
+
}
|
|
11
|
+
const looksLikePath = input.includes("\\") || input.includes("/");
|
|
12
|
+
const hasExt = /\.[A-Za-z0-9]+$/.test(input);
|
|
13
|
+
if (!looksLikePath && !hasExt) {
|
|
14
|
+
return `${input}.cmd`;
|
|
15
|
+
}
|
|
16
|
+
return input;
|
|
17
|
+
}
|
|
18
|
+
function geminiVersion() {
|
|
19
|
+
const command = resolveCommand(process.env.SDD_GEMINI_BIN?.trim() || "gemini");
|
|
20
|
+
const useShell = process.platform === "win32" && command.toLowerCase().endsWith(".cmd");
|
|
21
|
+
const result = (0, child_process_1.spawnSync)(command, ["--version"], {
|
|
22
|
+
encoding: "utf-8",
|
|
23
|
+
shell: useShell
|
|
24
|
+
});
|
|
25
|
+
if (result.status !== 0) {
|
|
26
|
+
return { ok: false, output: "", error: result.stderr || "gemini not available" };
|
|
27
|
+
}
|
|
28
|
+
return { ok: true, output: result.stdout.trim() };
|
|
29
|
+
}
|
|
30
|
+
function geminiExec(prompt) {
|
|
31
|
+
const command = resolveCommand(process.env.SDD_GEMINI_BIN?.trim() || "gemini");
|
|
32
|
+
const model = process.env.SDD_GEMINI_MODEL?.trim();
|
|
33
|
+
const useShell = process.platform === "win32" && command.toLowerCase().endsWith(".cmd");
|
|
34
|
+
const normalizedPrompt = prompt.replace(/\r?\n/g, "\\n");
|
|
35
|
+
const env = {
|
|
36
|
+
...process.env,
|
|
37
|
+
NO_COLOR: "1"
|
|
38
|
+
};
|
|
39
|
+
const modelArgs = model ? ["-m", model] : [];
|
|
40
|
+
const runPrimary = (withModel) => useShell
|
|
41
|
+
? (0, child_process_1.spawnSync)(`${command} ${withModel && model ? `-m "${model.replace(/"/g, "\"\"")}" ` : ""}--prompt "${normalizedPrompt.replace(/"/g, "\"\"")}" --output-format json`, {
|
|
42
|
+
encoding: "utf-8",
|
|
43
|
+
shell: true,
|
|
44
|
+
env
|
|
45
|
+
})
|
|
46
|
+
: (0, child_process_1.spawnSync)(command, [...(withModel ? modelArgs : []), "--prompt", normalizedPrompt, "--output-format", "json"], {
|
|
47
|
+
encoding: "utf-8",
|
|
48
|
+
shell: false,
|
|
49
|
+
env
|
|
50
|
+
});
|
|
51
|
+
const runFallback = (withModel) => useShell
|
|
52
|
+
? (0, child_process_1.spawnSync)(`${command} ${withModel && model ? `-m "${model.replace(/"/g, "\"\"")}" ` : ""}--prompt "${normalizedPrompt.replace(/"/g, "\"\"")}"`, {
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
shell: true,
|
|
55
|
+
env
|
|
56
|
+
})
|
|
57
|
+
: (0, child_process_1.spawnSync)(command, [...(withModel ? modelArgs : []), "--prompt", normalizedPrompt], {
|
|
58
|
+
encoding: "utf-8",
|
|
59
|
+
shell: false,
|
|
60
|
+
env
|
|
61
|
+
});
|
|
62
|
+
let result = runPrimary(true);
|
|
63
|
+
if (result.status !== 0 && model) {
|
|
64
|
+
result = runPrimary(false);
|
|
65
|
+
}
|
|
66
|
+
if (result.status !== 0) {
|
|
67
|
+
result = runFallback(true);
|
|
68
|
+
}
|
|
69
|
+
if (result.status !== 0 && model) {
|
|
70
|
+
result = runFallback(false);
|
|
71
|
+
}
|
|
72
|
+
if (result.status !== 0) {
|
|
73
|
+
return { ok: false, output: result.stdout || "", error: result.stderr || "gemini exec failed" };
|
|
74
|
+
}
|
|
75
|
+
return { ok: true, output: result.stdout.trim() };
|
|
76
|
+
}
|
|
77
|
+
exports.geminiProvider = {
|
|
78
|
+
id: "gemini",
|
|
79
|
+
label: "Gemini",
|
|
80
|
+
version: geminiVersion,
|
|
81
|
+
exec: geminiExec
|
|
82
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AIProvider, ProviderId, ProviderPreference } from "./types";
|
|
2
|
+
export declare function defaultProviderPreference(): ProviderPreference;
|
|
3
|
+
export declare function parseProviderPreference(input?: string): ProviderPreference | null;
|
|
4
|
+
export declare function listProviders(): AIProvider[];
|
|
5
|
+
export type ProviderResolution = {
|
|
6
|
+
ok: true;
|
|
7
|
+
provider: AIProvider;
|
|
8
|
+
selected: ProviderId;
|
|
9
|
+
requested: ProviderPreference;
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
requested: string;
|
|
13
|
+
reason: "invalid" | "unavailable";
|
|
14
|
+
details: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function resolveProvider(requested?: string): ProviderResolution;
|