create-squirrel-opencode-harness 1.0.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.
@@ -0,0 +1,131 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import { glob } from "glob";
4
+ import ejs from "ejs";
5
+ import chalk from "chalk";
6
+ import { fileURLToPath } from "url";
7
+ import { t } from "./i18n.js";
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const SOURCE_AGENTS_DIR = path.resolve(__dirname, "..", "agents");
11
+ const SOURCE_HARNESS_DIR = path.resolve(__dirname, "..", "harness");
12
+ function getTargetDirs() {
13
+ const targetBaseDir = path.resolve(process.cwd(), ".opencode");
14
+ return {
15
+ targetBaseDir,
16
+ targetAgentsDir: path.join(targetBaseDir, "agents"),
17
+ targetHarnessDir: path.join(targetBaseDir, "harness")
18
+ };
19
+ }
20
+ async function checkDirectories() {
21
+ console.log(chalk.gray(t("info.checkingDirs")));
22
+ const hasSourceAgents = await fs.pathExists(SOURCE_AGENTS_DIR);
23
+ const hasSourceHarness = await fs.pathExists(SOURCE_HARNESS_DIR);
24
+ if (!hasSourceAgents && !hasSourceHarness) {
25
+ throw new Error(
26
+ `${t("errors.sourceNotFound")}
27
+ - ${SOURCE_AGENTS_DIR}
28
+ - ${SOURCE_HARNESS_DIR}`
29
+ );
30
+ }
31
+ const { targetBaseDir, targetAgentsDir, targetHarnessDir } = getTargetDirs();
32
+ await fs.ensureDir(targetBaseDir);
33
+ console.log(chalk.gray(` \u2713 ${targetBaseDir}`));
34
+ await fs.ensureDir(targetAgentsDir);
35
+ console.log(chalk.gray(` \u2713 ${targetAgentsDir}`));
36
+ await fs.ensureDir(targetHarnessDir);
37
+ console.log(chalk.gray(` \u2713 ${targetHarnessDir}`));
38
+ return {
39
+ sourceAgentsDir: SOURCE_AGENTS_DIR,
40
+ sourceHarnessDir: SOURCE_HARNESS_DIR,
41
+ targetDir: targetBaseDir,
42
+ targetAgentsDir,
43
+ targetHarnessDir,
44
+ hasSourceAgents,
45
+ hasSourceHarness
46
+ };
47
+ }
48
+ async function transactionalCopy(model, dirs) {
49
+ console.log(chalk.gray(`
50
+ ${t("info.transactionCheck")}`));
51
+ const filesToCopy = [];
52
+ const existingFiles = [];
53
+ if (dirs.hasSourceAgents) {
54
+ const agentFiles = await glob("**/*.md", {
55
+ cwd: dirs.sourceAgentsDir,
56
+ absolute: true
57
+ });
58
+ for (const sourcePath of agentFiles) {
59
+ const relativePath = path.relative(dirs.sourceAgentsDir, sourcePath);
60
+ const targetPath = path.join(dirs.targetAgentsDir, relativePath);
61
+ if (await fs.pathExists(targetPath)) {
62
+ existingFiles.push(targetPath);
63
+ } else {
64
+ filesToCopy.push({
65
+ source: sourcePath,
66
+ target: targetPath,
67
+ isAgent: true,
68
+ relativePath
69
+ });
70
+ }
71
+ }
72
+ }
73
+ if (dirs.hasSourceHarness) {
74
+ const harnessFiles = await glob("**/*", {
75
+ cwd: dirs.sourceHarnessDir,
76
+ absolute: true,
77
+ nodir: true
78
+ });
79
+ for (const sourcePath of harnessFiles) {
80
+ const relativePath = path.relative(dirs.sourceHarnessDir, sourcePath);
81
+ const targetPath = path.join(dirs.targetHarnessDir, relativePath);
82
+ if (await fs.pathExists(targetPath)) {
83
+ existingFiles.push(targetPath);
84
+ } else {
85
+ filesToCopy.push({
86
+ source: sourcePath,
87
+ target: targetPath,
88
+ isAgent: false,
89
+ relativePath
90
+ });
91
+ }
92
+ }
93
+ }
94
+ if (existingFiles.length > 0) {
95
+ console.error(chalk.red(`
96
+ \u274C ${t("transaction.failed")}`));
97
+ for (const file of existingFiles) {
98
+ console.error(chalk.red(` - ${file}`));
99
+ }
100
+ throw new Error(t("errors.transactionFailed", { count: existingFiles.length }));
101
+ }
102
+ console.log(chalk.gray(` \u2713 ${t("info.noConflicts", { count: filesToCopy.length })}`));
103
+ console.log(chalk.gray(`
104
+ ${t("info.copyingFiles")}`));
105
+ for (const file of filesToCopy) {
106
+ await fs.ensureDir(path.dirname(file.target));
107
+ if (file.isAgent && file.relativePath.endsWith(".md")) {
108
+ const content = await fs.readFile(file.source, "utf-8");
109
+ const processed = await processTemplate(content, { model });
110
+ await fs.writeFile(file.target, processed);
111
+ } else {
112
+ await fs.copy(file.source, file.target);
113
+ }
114
+ console.log(chalk.gray(` \u2713 ${file.relativePath}`));
115
+ }
116
+ }
117
+ async function processTemplate(content, data) {
118
+ try {
119
+ const result = ejs.render(content, data, {
120
+ async: false
121
+ });
122
+ return result;
123
+ } catch (error) {
124
+ return content;
125
+ }
126
+ }
127
+ export {
128
+ checkDirectories,
129
+ transactionalCopy
130
+ };
131
+ //# sourceMappingURL=fileOps.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/fileOps.ts"],
4
+ "sourcesContent": ["import fs from 'fs-extra';\nimport path from 'path';\nimport { glob } from 'glob';\nimport ejs from 'ejs';\nimport chalk from 'chalk';\nimport { fileURLToPath } from 'url';\nimport { t } from './i18n.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Source directories (where templates are stored) - relative to src/ directory\nconst SOURCE_AGENTS_DIR = path.resolve(__dirname, '..', 'agents');\nconst SOURCE_HARNESS_DIR = path.resolve(__dirname, '..', 'harness');\n\nexport interface DirectoryInfo {\n sourceAgentsDir: string;\n sourceHarnessDir: string;\n targetDir: string;\n targetAgentsDir: string;\n targetHarnessDir: string;\n hasSourceAgents: boolean;\n hasSourceHarness: boolean;\n}\n\ninterface FileToCopy {\n source: string;\n target: string;\n isAgent: boolean;\n relativePath: string;\n}\n\n// Target directories (computed at runtime to support test isolation)\nfunction getTargetDirs() {\n const targetBaseDir = path.resolve(process.cwd(), '.opencode');\n return {\n targetBaseDir,\n targetAgentsDir: path.join(targetBaseDir, 'agents'),\n targetHarnessDir: path.join(targetBaseDir, 'harness')\n };\n}\n\nexport async function checkDirectories(): Promise<DirectoryInfo> {\n console.log(chalk.gray(t('info.checkingDirs')));\n\n // Check if source directories exist\n const hasSourceAgents = await fs.pathExists(SOURCE_AGENTS_DIR);\n const hasSourceHarness = await fs.pathExists(SOURCE_HARNESS_DIR);\n\n if (!hasSourceAgents && !hasSourceHarness) {\n throw new Error(\n `${t('errors.sourceNotFound')}\\n` +\n ` - ${SOURCE_AGENTS_DIR}\\n` +\n ` - ${SOURCE_HARNESS_DIR}`\n );\n }\n\n // Get target directories (computed at runtime)\n const { targetBaseDir, targetAgentsDir, targetHarnessDir } = getTargetDirs();\n\n // Create target directories if they don't exist\n await fs.ensureDir(targetBaseDir);\n console.log(chalk.gray(` \u2713 ${targetBaseDir}`));\n\n await fs.ensureDir(targetAgentsDir);\n console.log(chalk.gray(` \u2713 ${targetAgentsDir}`));\n\n await fs.ensureDir(targetHarnessDir);\n console.log(chalk.gray(` \u2713 ${targetHarnessDir}`));\n\n return {\n sourceAgentsDir: SOURCE_AGENTS_DIR,\n sourceHarnessDir: SOURCE_HARNESS_DIR,\n targetDir: targetBaseDir,\n targetAgentsDir,\n targetHarnessDir,\n hasSourceAgents,\n hasSourceHarness\n };\n}\n\nexport async function transactionalCopy(model: string, dirs: DirectoryInfo): Promise<void> {\n console.log(chalk.gray(`\\n${t('info.transactionCheck')}`));\n\n // Collect all files that need to be copied\n const filesToCopy: FileToCopy[] = [];\n const existingFiles: string[] = [];\n\n // Process agents directory\n if (dirs.hasSourceAgents) {\n const agentFiles = await glob('**/*.md', {\n cwd: dirs.sourceAgentsDir,\n absolute: true\n });\n\n for (const sourcePath of agentFiles) {\n const relativePath = path.relative(dirs.sourceAgentsDir, sourcePath);\n const targetPath = path.join(dirs.targetAgentsDir, relativePath);\n\n if (await fs.pathExists(targetPath)) {\n existingFiles.push(targetPath);\n } else {\n filesToCopy.push({\n source: sourcePath,\n target: targetPath,\n isAgent: true,\n relativePath\n });\n }\n }\n }\n\n // Process harness directory\n if (dirs.hasSourceHarness) {\n const harnessFiles = await glob('**/*', {\n cwd: dirs.sourceHarnessDir,\n absolute: true,\n nodir: true\n });\n\n for (const sourcePath of harnessFiles) {\n const relativePath = path.relative(dirs.sourceHarnessDir, sourcePath);\n const targetPath = path.join(dirs.targetHarnessDir, relativePath);\n\n if (await fs.pathExists(targetPath)) {\n existingFiles.push(targetPath);\n } else {\n filesToCopy.push({\n source: sourcePath,\n target: targetPath,\n isAgent: false,\n relativePath\n });\n }\n }\n }\n\n // Transaction check: if any file exists, abort\n if (existingFiles.length > 0) {\n console.error(chalk.red(`\\n\u274C ${t('transaction.failed')}`));\n for (const file of existingFiles) {\n console.error(chalk.red(` - ${file}`));\n }\n throw new Error(t('errors.transactionFailed', { count: existingFiles.length }));\n }\n\n console.log(chalk.gray(` \u2713 ${t('info.noConflicts', { count: filesToCopy.length })}`));\n\n // All checks passed, now perform the copy\n console.log(chalk.gray(`\\n${t('info.copyingFiles')}`));\n\n for (const file of filesToCopy) {\n await fs.ensureDir(path.dirname(file.target));\n\n if (file.isAgent && file.relativePath.endsWith('.md')) {\n // Process template for agent markdown files\n const content = await fs.readFile(file.source, 'utf-8');\n const processed = await processTemplate(content, { model });\n await fs.writeFile(file.target, processed);\n } else {\n // Copy as-is for non-agent files\n await fs.copy(file.source, file.target);\n }\n\n console.log(chalk.gray(` \u2713 ${file.relativePath}`));\n }\n}\n\nasync function processTemplate(content: string, data: Record<string, string>): Promise<string> {\n // Use EJS to render the template\n // The model variable will be replaced in the template\n try {\n const result = ejs.render(content, data, {\n async: false\n });\n return result;\n } catch (error) {\n // If EJS parsing fails, return original content\n // This handles files that may not be valid EJS templates\n return content;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/dist/i18n.js ADDED
@@ -0,0 +1,39 @@
1
+ import i18next from "i18next";
2
+ import Backend from "i18next-fs-backend";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ let initialized = false;
8
+ async function initI18n(language = "en") {
9
+ if (initialized) {
10
+ if (i18next.language !== language) {
11
+ await i18next.changeLanguage(language);
12
+ }
13
+ return i18next;
14
+ }
15
+ await i18next.use(Backend).init({
16
+ lng: language,
17
+ fallbackLng: "en",
18
+ backend: {
19
+ loadPath: path.join(__dirname, "../locales/{{lng}}/{{ns}}.json")
20
+ },
21
+ ns: ["translation"],
22
+ defaultNS: "translation",
23
+ interpolation: {
24
+ escapeValue: false
25
+ // Disable escaping for CLI output
26
+ }
27
+ });
28
+ initialized = true;
29
+ return i18next;
30
+ }
31
+ function t(key, options) {
32
+ return i18next.t(key, options);
33
+ }
34
+ export {
35
+ i18next,
36
+ initI18n,
37
+ t
38
+ };
39
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/i18n.ts"],
4
+ "sourcesContent": ["import i18next from 'i18next';\nimport Backend from 'i18next-fs-backend';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nlet initialized = false;\n\nexport async function initI18n(language: string = 'en'): Promise<typeof i18next> {\n if (initialized) {\n if (i18next.language !== language) {\n await i18next.changeLanguage(language);\n }\n return i18next;\n }\n\n await i18next.use(Backend).init({\n lng: language,\n fallbackLng: 'en',\n backend: {\n loadPath: path.join(__dirname, '../locales/{{lng}}/{{ns}}.json')\n },\n ns: ['translation'],\n defaultNS: 'translation',\n interpolation: {\n escapeValue: false // Disable escaping for CLI output\n }\n });\n\n initialized = true;\n return i18next;\n}\n\nexport function t(key: string, options?: Record<string, string | number>): string {\n return i18next.t(key, options);\n}\n\nexport { i18next };\n"],
5
+ "mappings": "AAAA,OAAO,aAAa;AACpB,OAAO,aAAa;AACpB,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,MAAM,aAAa,cAAc,YAAY,GAAG;AAChD,MAAM,YAAY,KAAK,QAAQ,UAAU;AAEzC,IAAI,cAAc;AAElB,eAAsB,SAAS,WAAmB,MAA+B;AAC/E,MAAI,aAAa;AACf,QAAI,QAAQ,aAAa,UAAU;AACjC,YAAM,QAAQ,eAAe,QAAQ;AAAA,IACvC;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,IAAI,OAAO,EAAE,KAAK;AAAA,IAC9B,KAAK;AAAA,IACL,aAAa;AAAA,IACb,SAAS;AAAA,MACP,UAAU,KAAK,KAAK,WAAW,gCAAgC;AAAA,IACjE;AAAA,IACA,IAAI,CAAC,aAAa;AAAA,IAClB,WAAW;AAAA,IACX,eAAe;AAAA,MACb,aAAa;AAAA;AAAA,IACf;AAAA,EACF,CAAC;AAED,gBAAc;AACd,SAAO;AACT;AAEO,SAAS,EAAE,KAAa,SAAmD;AAChF,SAAO,QAAQ,EAAE,KAAK,OAAO;AAC/B;",
6
+ "names": []
7
+ }
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ import chalk from "chalk";
2
+ import { resolveInput } from "./input.js";
3
+ import { checkDirectories, transactionalCopy } from "./fileOps.js";
4
+ import { initI18n, t } from "./i18n.js";
5
+ async function run(options = {}, lang = "en") {
6
+ await initI18n(lang);
7
+ console.log(chalk.blue(`\u{1F43F}\uFE0F ${t("title")}
8
+ `));
9
+ const model = await resolveInput(options);
10
+ if (!model) {
11
+ throw new Error(t("errors.modelRequired"));
12
+ }
13
+ console.log(chalk.gray(t("info.usingModel", { model }) + "\n"));
14
+ const dirs = await checkDirectories();
15
+ await transactionalCopy(model, dirs);
16
+ console.log(chalk.green(`
17
+ \u2705 ${t("info.success")}`));
18
+ console.log(chalk.gray(` ${t("info.location", { path: dirs.targetDir })}`));
19
+ }
20
+ export {
21
+ run
22
+ };
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["import chalk from 'chalk';\nimport { resolveInput } from './input.js';\nimport { checkDirectories, transactionalCopy, type DirectoryInfo } from './fileOps.js';\nimport { initI18n, t } from './i18n.js';\n\nexport interface RunOptions {\n positionalModel?: string;\n model?: string;\n interactive?: boolean;\n stdin?: boolean;\n lang?: string;\n}\n\nexport async function run(options: RunOptions = {}, lang: string = 'en'): Promise<void> {\n // Ensure i18n is initialized\n await initI18n(lang);\n\n console.log(chalk.blue(`\uD83D\uDC3F\uFE0F ${t('title')}\\n`));\n\n // Step 1: Resolve model input (from CLI args, stdin, or interactive)\n const model = await resolveInput(options);\n\n if (!model) {\n throw new Error(t('errors.modelRequired'));\n }\n\n console.log(chalk.gray(t('info.usingModel', { model }) + '\\n'));\n\n // Step 2: Check and create directories\n const dirs: DirectoryInfo = await checkDirectories();\n\n // Step 3: Transactional copy with template processing\n await transactionalCopy(model, dirs);\n\n console.log(chalk.green(`\\n\u2705 ${t('info.success')}`));\n console.log(chalk.gray(` ${t('info.location', { path: dirs.targetDir })}`));\n}\n"],
5
+ "mappings": "AAAA,OAAO,WAAW;AAClB,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB,yBAA6C;AACxE,SAAS,UAAU,SAAS;AAU5B,eAAsB,IAAI,UAAsB,CAAC,GAAG,OAAe,MAAqB;AAEtF,QAAM,SAAS,IAAI;AAEnB,UAAQ,IAAI,MAAM,KAAK,oBAAQ,EAAE,OAAO,CAAC;AAAA,CAAI,CAAC;AAG9C,QAAM,QAAQ,MAAM,aAAa,OAAO;AAExC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,EAAE,sBAAsB,CAAC;AAAA,EAC3C;AAEA,UAAQ,IAAI,MAAM,KAAK,EAAE,mBAAmB,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC;AAG9D,QAAM,OAAsB,MAAM,iBAAiB;AAGnD,QAAM,kBAAkB,OAAO,IAAI;AAEnC,UAAQ,IAAI,MAAM,MAAM;AAAA,SAAO,EAAE,cAAc,CAAC,EAAE,CAAC;AACnD,UAAQ,IAAI,MAAM,KAAK,MAAM,EAAE,iBAAiB,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,CAAC;AAC9E;",
6
+ "names": []
7
+ }
package/dist/input.js ADDED
@@ -0,0 +1,63 @@
1
+ import inquirer from "inquirer";
2
+ import { t } from "./i18n.js";
3
+ async function resolveInput(options) {
4
+ if (options.positionalModel) {
5
+ return options.positionalModel;
6
+ }
7
+ if (options.model) {
8
+ return options.model;
9
+ }
10
+ if (options.stdin) {
11
+ return readStdin();
12
+ }
13
+ if (options.interactive || !hasModelInput(options)) {
14
+ return promptInteractive();
15
+ }
16
+ return null;
17
+ }
18
+ function hasModelInput(options) {
19
+ return !!(options.positionalModel || options.model || options.stdin);
20
+ }
21
+ async function readStdin() {
22
+ return new Promise((resolve, reject) => {
23
+ let data = "";
24
+ process.stdin.setEncoding("utf8");
25
+ process.stdin.on("data", (chunk) => {
26
+ data += chunk;
27
+ });
28
+ process.stdin.on("end", () => {
29
+ const trimmed = data.trim();
30
+ if (!trimmed) {
31
+ reject(new Error(t("errors.stdinNoData")));
32
+ } else {
33
+ resolve(trimmed);
34
+ }
35
+ });
36
+ process.stdin.on("error", (err) => {
37
+ reject(new Error(t("errors.stdinReadError", { message: err.message })));
38
+ });
39
+ if (process.stdin.isTTY) {
40
+ reject(new Error(t("errors.stdinNotAvailable")));
41
+ }
42
+ });
43
+ }
44
+ async function promptInteractive() {
45
+ const answers = await inquirer.prompt([
46
+ {
47
+ type: "input",
48
+ name: "model",
49
+ message: t("prompts.enterModel"),
50
+ validate: (input) => {
51
+ if (!input || input.trim() === "") {
52
+ return t("prompts.modelRequired");
53
+ }
54
+ return true;
55
+ }
56
+ }
57
+ ]);
58
+ return answers.model.trim();
59
+ }
60
+ export {
61
+ resolveInput
62
+ };
63
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/input.ts"],
4
+ "sourcesContent": ["import inquirer from 'inquirer';\nimport { t } from './i18n.js';\n\nexport interface InputOptions {\n positionalModel?: string;\n model?: string;\n interactive?: boolean;\n stdin?: boolean;\n}\n\nexport async function resolveInput(options: InputOptions): Promise<string | null> {\n // Priority 1: CLI argument (positional)\n if (options.positionalModel) {\n return options.positionalModel;\n }\n\n // Priority 2: CLI option --model\n if (options.model) {\n return options.model;\n }\n\n // Priority 3: Stdin\n if (options.stdin) {\n return readStdin();\n }\n\n // Priority 4: Interactive mode (if requested or as fallback)\n if (options.interactive || !hasModelInput(options)) {\n return promptInteractive();\n }\n\n return null;\n}\n\nfunction hasModelInput(options: InputOptions): boolean {\n return !!(options.positionalModel || options.model || options.stdin);\n}\n\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = '';\n\n process.stdin.setEncoding('utf8');\n\n process.stdin.on('data', (chunk: string) => {\n data += chunk;\n });\n\n process.stdin.on('end', () => {\n const trimmed = data.trim();\n if (!trimmed) {\n reject(new Error(t('errors.stdinNoData')));\n } else {\n resolve(trimmed);\n }\n });\n\n process.stdin.on('error', (err: Error) => {\n reject(new Error(t('errors.stdinReadError', { message: err.message })));\n });\n\n // If stdin is a TTY (not piped), we need to handle it differently\n if (process.stdin.isTTY) {\n reject(new Error(t('errors.stdinNotAvailable')));\n }\n });\n}\n\nasync function promptInteractive(): Promise<string> {\n const answers = await inquirer.prompt([\n {\n type: 'input',\n name: 'model',\n message: t('prompts.enterModel'),\n validate: (input: string) => {\n if (!input || input.trim() === '') {\n return t('prompts.modelRequired');\n }\n return true;\n }\n }\n ]);\n\n return (answers.model as string).trim();\n}\n"],
5
+ "mappings": "AAAA,OAAO,cAAc;AACrB,SAAS,SAAS;AASlB,eAAsB,aAAa,SAA+C;AAEhF,MAAI,QAAQ,iBAAiB;AAC3B,WAAO,QAAQ;AAAA,EACjB;AAGA,MAAI,QAAQ,OAAO;AACjB,WAAO,QAAQ;AAAA,EACjB;AAGA,MAAI,QAAQ,OAAO;AACjB,WAAO,UAAU;AAAA,EACnB;AAGA,MAAI,QAAQ,eAAe,CAAC,cAAc,OAAO,GAAG;AAClD,WAAO,kBAAkB;AAAA,EAC3B;AAEA,SAAO;AACT;AAEA,SAAS,cAAc,SAAgC;AACrD,SAAO,CAAC,EAAE,QAAQ,mBAAmB,QAAQ,SAAS,QAAQ;AAChE;AAEA,eAAe,YAA6B;AAC1C,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,OAAO;AAEX,YAAQ,MAAM,YAAY,MAAM;AAEhC,YAAQ,MAAM,GAAG,QAAQ,CAAC,UAAkB;AAC1C,cAAQ;AAAA,IACV,CAAC;AAED,YAAQ,MAAM,GAAG,OAAO,MAAM;AAC5B,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,SAAS;AACZ,eAAO,IAAI,MAAM,EAAE,oBAAoB,CAAC,CAAC;AAAA,MAC3C,OAAO;AACL,gBAAQ,OAAO;AAAA,MACjB;AAAA,IACF,CAAC;AAED,YAAQ,MAAM,GAAG,SAAS,CAAC,QAAe;AACxC,aAAO,IAAI,MAAM,EAAE,yBAAyB,EAAE,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC;AAAA,IACxE,CAAC;AAGD,QAAI,QAAQ,MAAM,OAAO;AACvB,aAAO,IAAI,MAAM,EAAE,0BAA0B,CAAC,CAAC;AAAA,IACjD;AAAA,EACF,CAAC;AACH;AAEA,eAAe,oBAAqC;AAClD,QAAM,UAAU,MAAM,SAAS,OAAO;AAAA,IACpC;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,SAAS,EAAE,oBAAoB;AAAA,MAC/B,UAAU,CAAC,UAAkB;AAC3B,YAAI,CAAC,SAAS,MAAM,KAAK,MAAM,IAAI;AACjC,iBAAO,EAAE,uBAAuB;AAAA,QAClC;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAQ,QAAQ,MAAiB,KAAK;AACxC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "cli": {
3
+ "description": "Scaffold squirrel opencode harness into .opencode directory",
4
+ "modelOption": "Model identifier for agents (e.g., fireworks-ai/accounts/fireworks/routers/kimi-k2p5-turbo)",
5
+ "interactiveOption": "Use interactive mode to input model",
6
+ "stdinOption": "Read model from stdin",
7
+ "langOption": "Language (en/zh)",
8
+ "positionalModel": "Model identifier (positional argument)"
9
+ },
10
+ "errors": {
11
+ "modelRequired": "Model is required. Provide it via --model, positional argument, --stdin, or --interactive",
12
+ "sourceNotFound": "Source directories not found. Expected:",
13
+ "transactionFailed": "Transaction aborted: {{count}} file(s) already exist. Remove existing files or use a different target directory.",
14
+ "stdinNotAvailable": "Stdin is not available (no piped input detected)",
15
+ "stdinReadError": "Failed to read from stdin: {{message}}",
16
+ "stdinNoData": "No data received from stdin"
17
+ },
18
+ "prompts": {
19
+ "enterModel": "Enter the model identifier:",
20
+ "modelRequired": "Model identifier is required"
21
+ },
22
+ "info": {
23
+ "checkingDirs": "Checking directories...",
24
+ "transactionCheck": "Performing transaction check...",
25
+ "copyingFiles": "Copying files...",
26
+ "usingModel": "Using model: {{model}}",
27
+ "noConflicts": "No conflicts found ({{count}} files to copy)",
28
+ "success": "Harness scaffolded successfully!",
29
+ "location": "Location: {{path}}"
30
+ },
31
+ "transaction": {
32
+ "failed": "Transaction failed: The following files already exist:"
33
+ },
34
+ "title": "Squirrel Opencode Harness Scaffolder"
35
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "cli": {
3
+ "description": "将松鼠Opencode Harness脚手架搭建到.opencode目录",
4
+ "modelOption": "代理模型标识符 (例如: fireworks-ai/accounts/fireworks/routers/kimi-k2p5-turbo)",
5
+ "interactiveOption": "使用交互式模式输入模型",
6
+ "stdinOption": "从标准输入读取模型",
7
+ "langOption": "语言 (en/zh)",
8
+ "positionalModel": "模型标识符 (位置参数)"
9
+ },
10
+ "errors": {
11
+ "modelRequired": "模型标识符是必需的。请通过 --model、位置参数、--stdin 或 --interactive 提供",
12
+ "sourceNotFound": "源目录未找到。期望路径:",
13
+ "transactionFailed": "事务中止: {{count}} 个文件已存在。请删除现有文件或使用不同的目标目录。",
14
+ "stdinNotAvailable": "标准输入不可用 (未检测到管道输入)",
15
+ "stdinReadError": "从标准输入读取失败: {{message}}",
16
+ "stdinNoData": "未从标准输入接收到数据"
17
+ },
18
+ "prompts": {
19
+ "enterModel": "请输入模型标识符:",
20
+ "modelRequired": "模型标识符是必需的"
21
+ },
22
+ "info": {
23
+ "checkingDirs": "正在检查目录...",
24
+ "transactionCheck": "正在执行事务检查...",
25
+ "copyingFiles": "正在复制文件...",
26
+ "usingModel": "使用模型: {{model}}",
27
+ "noConflicts": "未发现冲突 ({{count}} 个文件待复制)",
28
+ "success": "Harness脚手架搭建成功!",
29
+ "location": "位置: {{path}}"
30
+ },
31
+ "transaction": {
32
+ "failed": "事务失败: 以下文件已存在:"
33
+ },
34
+ "title": "松鼠Opencode Harness脚手架"
35
+ }
@@ -0,0 +1,18 @@
1
+ # Sprint Contract: Sprint [N] — [Name]
2
+
3
+ ## Scope
4
+ [What this sprint will build, based on the spec]
5
+
6
+ ## Implementation Plan
7
+ [High-level approach]
8
+ - [Key technical decisions]
9
+ - [Component structure]
10
+ - [API endpoints if applicable]
11
+
12
+ ## Success Criteria
13
+ 1. **[Criterion]**: [How to verify — must be specific and testable]
14
+ 2. **[Criterion]**: [How to verify]
15
+ 3. **[Criterion]**: [How to verify]
16
+
17
+ ## Out of Scope for This Sprint
18
+ - [What is explicitly NOT being built this sprint]
@@ -0,0 +1,39 @@
1
+ # Evaluation: Sprint [N]
2
+
3
+ ## Overall Verdict: [PASS / FAIL]
4
+
5
+ ## Success Criteria Results
6
+ 1. **[Criterion]**: [PASS / FAIL]
7
+ - Expected: [what was expected]
8
+ - Actual: [what actually happened]
9
+ - Reproduction (if FAIL): [steps]
10
+
11
+ ## Bug Report
12
+ 1. **[Bug Title]**: [Severity: Critical/Major/Minor]
13
+ - Steps to reproduce: [...]
14
+ - Expected: [...]
15
+ - Actual: [...]
16
+ - Location: [file:line or UI location]
17
+
18
+ ## Scoring
19
+
20
+ ### Product Depth: [score]/10
21
+ [Justification]
22
+
23
+ ### Functionality: [score]/10
24
+ [Justification]
25
+
26
+ ### Visual Design: [score]/10
27
+ [Justification]
28
+
29
+ ### Code Quality: [score]/10
30
+ [Justification]
31
+
32
+ **Hard threshold**: Any dimension below 4/10 = automatic FAIL.
33
+
34
+ ## Detailed Critique
35
+ [Paragraph-form assessment with concrete examples]
36
+
37
+ ## Required Fixes (if FAIL)
38
+ 1. [Specific, actionable fix]
39
+ 2. [Specific, actionable fix]
@@ -0,0 +1,16 @@
1
+ # Harness Run Summary
2
+
3
+ ## Original Prompt
4
+ [The user's original prompt]
5
+
6
+ ## Sprints Completed
7
+
8
+ ### Sprint [N]: [Name] — [PASS/FAIL/PARTIAL]
9
+ - Evaluation rounds: [count]
10
+ - Issues found and addressed: [summary]
11
+
12
+ ## Final Assessment
13
+ [Overall assessment of the built application]
14
+
15
+ ## Known Gaps
16
+ [Issues that remain unresolved]
@@ -0,0 +1,14 @@
1
+ # Handoff: Sprint [N]
2
+
3
+ ## Status: [Ready for QA / Not Ready — explain why]
4
+
5
+ ## What to Test
6
+ [Step-by-step instructions for the evaluator]
7
+ 1. [Step 1]
8
+ 2. [Step 2]
9
+
10
+ ## Running the Application
11
+ [How to start/restart the app]
12
+
13
+ ## Known Gaps
14
+ [Honest assessment of anything not fully working]
@@ -0,0 +1,14 @@
1
+ # Self-Evaluation: Sprint [N]
2
+
3
+ ## What Was Built
4
+ [Summary of implemented features]
5
+
6
+ ## Success Criteria Check
7
+ - [x] Criterion 1: [notes]
8
+ - [ ] Criterion 2: [notes on what's missing]
9
+
10
+ ## Known Issues
11
+ - [Bugs, limitations, or deviations from the contract]
12
+
13
+ ## Decisions Made
14
+ - [Significant implementation decisions and rationale]
@@ -0,0 +1,53 @@
1
+ # Product Specification: [Product Name]
2
+
3
+ ## Overview
4
+ [2-3 paragraph vision statement describing what this product is, who it's for, and why it matters]
5
+
6
+ ## Core Features
7
+
8
+ 1. **[Feature Name]**: [Description]
9
+ - User stories:
10
+ - As a [type of user], I can [action] so that [benefit]
11
+ - Acceptance criteria:
12
+ - [Given/When/Then or testable condition]
13
+
14
+ 2. **[Feature Name]**: [Description]
15
+ - User stories:
16
+ - As a [type of user], I can [action] so that [benefit]
17
+ - Acceptance criteria:
18
+ - [Given/When/Then or testable condition]
19
+
20
+ ## AI Integration
21
+ - AI Agent capabilities: [what the agent can do]
22
+ - AI Agent tools: [what tools/functions the agent has access to]
23
+ - User interaction model: [how the user invokes and interacts with the AI]
24
+
25
+ ## Technical Architecture
26
+ - Frontend: [framework, styling approach]
27
+ - Backend: [framework, database]
28
+ - Key patterns: [architectural patterns]
29
+
30
+ ## Visual Design Direction
31
+ - Aesthetic: [direction statement]
32
+ - Color palette: [direction statement]
33
+ - Typography: [direction statement]
34
+ - Layout principles: [direction statement]
35
+
36
+ ## Sprint Breakdown
37
+
38
+ ### Sprint 1: [Name]
39
+ - Scope: [what's being built]
40
+ - Dependencies: none
41
+ - Delivers: [tangible output]
42
+ - Acceptance criteria:
43
+ - [testable condition]
44
+
45
+ ### Sprint 2: [Name]
46
+ - Scope: [what's being built]
47
+ - Dependencies: Sprint 1
48
+ - Delivers: [tangible output]
49
+ - Acceptance criteria:
50
+ - [testable condition]
51
+
52
+ ## Out of Scope
53
+ - [Explicit exclusions]
@@ -0,0 +1,8 @@
1
+ # Sprint Status
2
+
3
+ ## Current Sprint: 0 — Planning
4
+ ## Current Phase: initialization
5
+ ## Contract Status: n/a
6
+ ## Evaluation Status: n/a
7
+ ## Last Updated: [timestamp on initialization]
8
+ ## Notes: Harness initialized. Awaiting prompt.
@@ -0,0 +1,35 @@
1
+ {
2
+ "cli": {
3
+ "description": "Scaffold squirrel opencode harness into .opencode directory",
4
+ "modelOption": "Model identifier for agents (e.g., fireworks-ai/accounts/fireworks/routers/kimi-k2p5-turbo)",
5
+ "interactiveOption": "Use interactive mode to input model",
6
+ "stdinOption": "Read model from stdin",
7
+ "langOption": "Language (en/zh)",
8
+ "positionalModel": "Model identifier (positional argument)"
9
+ },
10
+ "errors": {
11
+ "modelRequired": "Model is required. Provide it via --model, positional argument, --stdin, or --interactive",
12
+ "sourceNotFound": "Source directories not found. Expected:",
13
+ "transactionFailed": "Transaction aborted: {{count}} file(s) already exist. Remove existing files or use a different target directory.",
14
+ "stdinNotAvailable": "Stdin is not available (no piped input detected)",
15
+ "stdinReadError": "Failed to read from stdin: {{message}}",
16
+ "stdinNoData": "No data received from stdin"
17
+ },
18
+ "prompts": {
19
+ "enterModel": "Enter the model identifier:",
20
+ "modelRequired": "Model identifier is required"
21
+ },
22
+ "info": {
23
+ "checkingDirs": "Checking directories...",
24
+ "transactionCheck": "Performing transaction check...",
25
+ "copyingFiles": "Copying files...",
26
+ "usingModel": "Using model: {{model}}",
27
+ "noConflicts": "No conflicts found ({{count}} files to copy)",
28
+ "success": "Harness scaffolded successfully!",
29
+ "location": "Location: {{path}}"
30
+ },
31
+ "transaction": {
32
+ "failed": "Transaction failed: The following files already exist:"
33
+ },
34
+ "title": "Squirrel Opencode Harness Scaffolder"
35
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "cli": {
3
+ "description": "将松鼠Opencode Harness脚手架搭建到.opencode目录",
4
+ "modelOption": "代理模型标识符 (例如: fireworks-ai/accounts/fireworks/routers/kimi-k2p5-turbo)",
5
+ "interactiveOption": "使用交互式模式输入模型",
6
+ "stdinOption": "从标准输入读取模型",
7
+ "langOption": "语言 (en/zh)",
8
+ "positionalModel": "模型标识符 (位置参数)"
9
+ },
10
+ "errors": {
11
+ "modelRequired": "模型标识符是必需的。请通过 --model、位置参数、--stdin 或 --interactive 提供",
12
+ "sourceNotFound": "源目录未找到。期望路径:",
13
+ "transactionFailed": "事务中止: {{count}} 个文件已存在。请删除现有文件或使用不同的目标目录。",
14
+ "stdinNotAvailable": "标准输入不可用 (未检测到管道输入)",
15
+ "stdinReadError": "从标准输入读取失败: {{message}}",
16
+ "stdinNoData": "未从标准输入接收到数据"
17
+ },
18
+ "prompts": {
19
+ "enterModel": "请输入模型标识符:",
20
+ "modelRequired": "模型标识符是必需的"
21
+ },
22
+ "info": {
23
+ "checkingDirs": "正在检查目录...",
24
+ "transactionCheck": "正在执行事务检查...",
25
+ "copyingFiles": "正在复制文件...",
26
+ "usingModel": "使用模型: {{model}}",
27
+ "noConflicts": "未发现冲突 ({{count}} 个文件待复制)",
28
+ "success": "Harness脚手架搭建成功!",
29
+ "location": "位置: {{path}}"
30
+ },
31
+ "transaction": {
32
+ "failed": "事务失败: 以下文件已存在:"
33
+ },
34
+ "title": "松鼠Opencode Harness脚手架"
35
+ }