@cluesmith/codev 1.5.10 → 1.5.12
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/dist/agent-farm/commands/architect.d.ts.map +1 -1
- package/dist/agent-farm/commands/architect.js +5 -20
- package/dist/agent-farm/commands/architect.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +35 -22
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/start.d.ts.map +1 -1
- package/dist/agent-farm/commands/start.js +35 -21
- package/dist/agent-farm/commands/start.js.map +1 -1
- package/dist/agent-farm/servers/dashboard-server.js +5 -53
- package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
- package/dist/agent-farm/servers/open-server.js +2 -11
- package/dist/agent-farm/servers/open-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +2 -54
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/utils/roles.d.ts +32 -0
- package/dist/agent-farm/utils/roles.d.ts.map +1 -0
- package/dist/agent-farm/utils/roles.js +43 -0
- package/dist/agent-farm/utils/roles.js.map +1 -0
- package/dist/agent-farm/utils/server-utils.d.ts +24 -0
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -0
- package/dist/agent-farm/utils/server-utils.js +66 -0
- package/dist/agent-farm/utils/server-utils.js.map +1 -0
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +37 -157
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +26 -138
- package/dist/commands/init.js.map +1 -1
- package/dist/lib/cli-prompts.d.ts +20 -0
- package/dist/lib/cli-prompts.d.ts.map +1 -0
- package/dist/lib/cli-prompts.js +51 -0
- package/dist/lib/cli-prompts.js.map +1 -0
- package/dist/lib/scaffold.d.ts +81 -0
- package/dist/lib/scaffold.d.ts.map +1 -0
- package/dist/lib/scaffold.js +189 -0
- package/dist/lib/scaffold.js.map +1 -0
- package/package.json +1 -1
- package/templates/3d-viewer.html +5 -5
- package/templates/dashboard/js/activity.js +4 -130
- package/templates/dashboard/js/dialogs.js +4 -49
- package/templates/dashboard/js/files.js +2 -29
- package/templates/dashboard/js/main.js +2 -31
- package/templates/dashboard/js/tabs.js +6 -30
- package/templates/dashboard/js/utils.js +199 -0
- package/templates/open.html +8 -8
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI prompt utilities
|
|
3
|
+
* Extracted from init.ts and adopt.ts to eliminate duplication
|
|
4
|
+
* (Maintenance Run 0004)
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Prompt user for text input
|
|
8
|
+
* @param question - The question to display
|
|
9
|
+
* @param defaultValue - Optional default value shown in brackets
|
|
10
|
+
* @returns The user's input or the default value
|
|
11
|
+
*/
|
|
12
|
+
export declare function prompt(question: string, defaultValue?: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Prompt for yes/no confirmation
|
|
15
|
+
* @param question - The question to display
|
|
16
|
+
* @param defaultYes - Whether 'yes' is the default (true) or 'no' is (false)
|
|
17
|
+
* @returns true if user confirmed, false otherwise
|
|
18
|
+
*/
|
|
19
|
+
export declare function confirm(question: string, defaultYes?: boolean): Promise<boolean>;
|
|
20
|
+
//# sourceMappingURL=cli-prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-prompts.d.ts","sourceRoot":"","sources":["../../src/lib/cli-prompts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;;GAKG;AACH,wBAAsB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAarF;AAED;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBnF"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI prompt utilities
|
|
3
|
+
* Extracted from init.ts and adopt.ts to eliminate duplication
|
|
4
|
+
* (Maintenance Run 0004)
|
|
5
|
+
*/
|
|
6
|
+
import * as readline from 'node:readline';
|
|
7
|
+
/**
|
|
8
|
+
* Prompt user for text input
|
|
9
|
+
* @param question - The question to display
|
|
10
|
+
* @param defaultValue - Optional default value shown in brackets
|
|
11
|
+
* @returns The user's input or the default value
|
|
12
|
+
*/
|
|
13
|
+
export async function prompt(question, defaultValue) {
|
|
14
|
+
const rl = readline.createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout,
|
|
17
|
+
});
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const promptText = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
|
20
|
+
rl.question(promptText, (answer) => {
|
|
21
|
+
rl.close();
|
|
22
|
+
resolve(answer.trim() || defaultValue || '');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Prompt for yes/no confirmation
|
|
28
|
+
* @param question - The question to display
|
|
29
|
+
* @param defaultYes - Whether 'yes' is the default (true) or 'no' is (false)
|
|
30
|
+
* @returns true if user confirmed, false otherwise
|
|
31
|
+
*/
|
|
32
|
+
export async function confirm(question, defaultYes = true) {
|
|
33
|
+
const rl = readline.createInterface({
|
|
34
|
+
input: process.stdin,
|
|
35
|
+
output: process.stdout,
|
|
36
|
+
});
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const hint = defaultYes ? '[Y/n]' : '[y/N]';
|
|
39
|
+
rl.question(`${question} ${hint}: `, (answer) => {
|
|
40
|
+
rl.close();
|
|
41
|
+
const normalized = answer.trim().toLowerCase();
|
|
42
|
+
if (normalized === '') {
|
|
43
|
+
resolve(defaultYes);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
resolve(normalized === 'y' || normalized === 'yes');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=cli-prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-prompts.js","sourceRoot":"","sources":["../../src/lib/cli-prompts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,QAAgB,EAAE,YAAqB;IAClE,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,CAAC;QACtF,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE;YACjC,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,YAAY,IAAI,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,QAAgB,EAAE,UAAU,GAAG,IAAI;IAC/D,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC5C,EAAE,CAAC,QAAQ,CAAC,GAAG,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE;YAC9C,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC/C,IAAI,UAAU,KAAK,EAAE,EAAE,CAAC;gBACtB,OAAO,CAAC,UAAU,CAAC,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,CAAC,CAAC;YACtD,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold utilities for codev init and adopt commands
|
|
3
|
+
* Extracted to eliminate duplication (Maintenance Run 0004)
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Standard gitignore entries for codev projects
|
|
7
|
+
*/
|
|
8
|
+
export declare const CODEV_GITIGNORE_ENTRIES = "# Codev\n.agent-farm/\n.consult/\ncodev/.update-hashes.json\n.builders/\n";
|
|
9
|
+
/**
|
|
10
|
+
* Full gitignore content for new projects
|
|
11
|
+
*/
|
|
12
|
+
export declare const FULL_GITIGNORE_CONTENT = "# Codev\n.agent-farm/\n.consult/\ncodev/.update-hashes.json\n.builders/\n\n# Dependencies\nnode_modules/\n\n# Build output\ndist/\n\n# OS files\n.DS_Store\n*.swp\n*.swo\n";
|
|
13
|
+
interface CreateUserDirsOptions {
|
|
14
|
+
skipExisting?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface CreateUserDirsResult {
|
|
17
|
+
created: string[];
|
|
18
|
+
skipped: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create user data directories (specs, plans, reviews) with .gitkeep files
|
|
22
|
+
*/
|
|
23
|
+
export declare function createUserDirs(targetDir: string, options?: CreateUserDirsOptions): CreateUserDirsResult;
|
|
24
|
+
interface CopyProjectlistOptions {
|
|
25
|
+
skipExisting?: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface CopyProjectlistResult {
|
|
28
|
+
copied: boolean;
|
|
29
|
+
skipped?: boolean;
|
|
30
|
+
usedFallback?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Copy projectlist.md from skeleton template, with inline fallback
|
|
34
|
+
*/
|
|
35
|
+
export declare function copyProjectlist(targetDir: string, skeletonDir: string, options?: CopyProjectlistOptions): CopyProjectlistResult;
|
|
36
|
+
interface CopyProjectlistArchiveResult {
|
|
37
|
+
copied: boolean;
|
|
38
|
+
skipped?: boolean;
|
|
39
|
+
templateNotFound?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Copy projectlist-archive.md from skeleton template
|
|
43
|
+
*/
|
|
44
|
+
export declare function copyProjectlistArchive(targetDir: string, skeletonDir: string, options?: CopyProjectlistOptions): CopyProjectlistArchiveResult;
|
|
45
|
+
interface CopyResourceTemplatesOptions {
|
|
46
|
+
skipExisting?: boolean;
|
|
47
|
+
}
|
|
48
|
+
interface CopyResourceTemplatesResult {
|
|
49
|
+
copied: string[];
|
|
50
|
+
skipped: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Copy resource templates (lessons-learned.md, arch.md)
|
|
54
|
+
*/
|
|
55
|
+
export declare function copyResourceTemplates(targetDir: string, skeletonDir: string, options?: CopyResourceTemplatesOptions): CopyResourceTemplatesResult;
|
|
56
|
+
interface CopyRootFilesOptions {
|
|
57
|
+
handleConflicts?: boolean;
|
|
58
|
+
}
|
|
59
|
+
interface CopyRootFilesResult {
|
|
60
|
+
copied: string[];
|
|
61
|
+
conflicts: string[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Copy root files (CLAUDE.md, AGENTS.md) with project name substitution
|
|
65
|
+
*/
|
|
66
|
+
export declare function copyRootFiles(targetDir: string, skeletonDir: string, projectName: string, options?: CopyRootFilesOptions): CopyRootFilesResult;
|
|
67
|
+
/**
|
|
68
|
+
* Create a new .gitignore file with full content (for init)
|
|
69
|
+
*/
|
|
70
|
+
export declare function createGitignore(targetDir: string): void;
|
|
71
|
+
interface UpdateGitignoreResult {
|
|
72
|
+
updated: boolean;
|
|
73
|
+
created: boolean;
|
|
74
|
+
alreadyPresent: boolean;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Update existing .gitignore or create if not exists (for adopt)
|
|
78
|
+
*/
|
|
79
|
+
export declare function updateGitignore(targetDir: string): UpdateGitignoreResult;
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=scaffold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/lib/scaffold.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;GAEG;AACH,eAAO,MAAM,uBAAuB,8EAKnC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,+KAWlC,CAAC;AA0BF,UAAU,qBAAqB;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,oBAAoB;IAC5B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAkBtB;AAED,UAAU,sBAAsB;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,qBAAqB;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CAiBvB;AAED,UAAU,4BAA4B;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,sBAA2B,GACnC,4BAA4B,CAe9B;AAED,UAAU,4BAA4B;IACpC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,2BAA2B;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,4BAAiC,GACzC,2BAA2B,CA4B7B;AAED,UAAU,oBAAoB;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,oBAAyB,GACjC,mBAAmB,CA+BrB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGvD;AAED,UAAU,qBAAqB;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,qBAAqB,CAexE"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold utilities for codev init and adopt commands
|
|
3
|
+
* Extracted to eliminate duplication (Maintenance Run 0004)
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
/**
|
|
8
|
+
* Standard gitignore entries for codev projects
|
|
9
|
+
*/
|
|
10
|
+
export const CODEV_GITIGNORE_ENTRIES = `# Codev
|
|
11
|
+
.agent-farm/
|
|
12
|
+
.consult/
|
|
13
|
+
codev/.update-hashes.json
|
|
14
|
+
.builders/
|
|
15
|
+
`;
|
|
16
|
+
/**
|
|
17
|
+
* Full gitignore content for new projects
|
|
18
|
+
*/
|
|
19
|
+
export const FULL_GITIGNORE_CONTENT = `${CODEV_GITIGNORE_ENTRIES}
|
|
20
|
+
# Dependencies
|
|
21
|
+
node_modules/
|
|
22
|
+
|
|
23
|
+
# Build output
|
|
24
|
+
dist/
|
|
25
|
+
|
|
26
|
+
# OS files
|
|
27
|
+
.DS_Store
|
|
28
|
+
*.swp
|
|
29
|
+
*.swo
|
|
30
|
+
`;
|
|
31
|
+
/**
|
|
32
|
+
* Inline fallback template for projectlist.md
|
|
33
|
+
*/
|
|
34
|
+
const PROJECTLIST_FALLBACK = `# Project List
|
|
35
|
+
|
|
36
|
+
Track all projects here. See codev documentation for status values.
|
|
37
|
+
|
|
38
|
+
\`\`\`yaml
|
|
39
|
+
projects:
|
|
40
|
+
- id: "0001"
|
|
41
|
+
title: "Example Project"
|
|
42
|
+
summary: "Brief description"
|
|
43
|
+
status: conceived
|
|
44
|
+
priority: medium
|
|
45
|
+
files:
|
|
46
|
+
spec: null
|
|
47
|
+
plan: null
|
|
48
|
+
review: null
|
|
49
|
+
dependencies: []
|
|
50
|
+
tags: []
|
|
51
|
+
notes: "Replace with your first project"
|
|
52
|
+
\`\`\`
|
|
53
|
+
`;
|
|
54
|
+
/**
|
|
55
|
+
* Create user data directories (specs, plans, reviews) with .gitkeep files
|
|
56
|
+
*/
|
|
57
|
+
export function createUserDirs(targetDir, options = {}) {
|
|
58
|
+
const { skipExisting = false } = options;
|
|
59
|
+
const userDirs = ['specs', 'plans', 'reviews'];
|
|
60
|
+
const created = [];
|
|
61
|
+
const skipped = [];
|
|
62
|
+
for (const dir of userDirs) {
|
|
63
|
+
const dirPath = path.join(targetDir, 'codev', dir);
|
|
64
|
+
if (skipExisting && fs.existsSync(dirPath)) {
|
|
65
|
+
skipped.push(dir);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
69
|
+
fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
|
|
70
|
+
created.push(dir);
|
|
71
|
+
}
|
|
72
|
+
return { created, skipped };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Copy projectlist.md from skeleton template, with inline fallback
|
|
76
|
+
*/
|
|
77
|
+
export function copyProjectlist(targetDir, skeletonDir, options = {}) {
|
|
78
|
+
const { skipExisting = false } = options;
|
|
79
|
+
const projectlistPath = path.join(targetDir, 'codev', 'projectlist.md');
|
|
80
|
+
if (skipExisting && fs.existsSync(projectlistPath)) {
|
|
81
|
+
return { copied: false, skipped: true };
|
|
82
|
+
}
|
|
83
|
+
const templatePath = path.join(skeletonDir, 'templates', 'projectlist.md');
|
|
84
|
+
if (fs.existsSync(templatePath)) {
|
|
85
|
+
fs.copyFileSync(templatePath, projectlistPath);
|
|
86
|
+
return { copied: true };
|
|
87
|
+
}
|
|
88
|
+
// Fallback to inline template
|
|
89
|
+
fs.writeFileSync(projectlistPath, PROJECTLIST_FALLBACK);
|
|
90
|
+
return { copied: true, usedFallback: true };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Copy projectlist-archive.md from skeleton template
|
|
94
|
+
*/
|
|
95
|
+
export function copyProjectlistArchive(targetDir, skeletonDir, options = {}) {
|
|
96
|
+
const { skipExisting = false } = options;
|
|
97
|
+
const archivePath = path.join(targetDir, 'codev', 'projectlist-archive.md');
|
|
98
|
+
if (skipExisting && fs.existsSync(archivePath)) {
|
|
99
|
+
return { copied: false, skipped: true };
|
|
100
|
+
}
|
|
101
|
+
const templatePath = path.join(skeletonDir, 'templates', 'projectlist-archive.md');
|
|
102
|
+
if (!fs.existsSync(templatePath)) {
|
|
103
|
+
return { copied: false, templateNotFound: true };
|
|
104
|
+
}
|
|
105
|
+
fs.copyFileSync(templatePath, archivePath);
|
|
106
|
+
return { copied: true };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Copy resource templates (lessons-learned.md, arch.md)
|
|
110
|
+
*/
|
|
111
|
+
export function copyResourceTemplates(targetDir, skeletonDir, options = {}) {
|
|
112
|
+
const { skipExisting = false } = options;
|
|
113
|
+
const resourcesDir = path.join(targetDir, 'codev', 'resources');
|
|
114
|
+
const copied = [];
|
|
115
|
+
const skipped = [];
|
|
116
|
+
// Ensure resources directory exists
|
|
117
|
+
if (!fs.existsSync(resourcesDir)) {
|
|
118
|
+
fs.mkdirSync(resourcesDir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
const templates = ['lessons-learned.md', 'arch.md'];
|
|
121
|
+
for (const template of templates) {
|
|
122
|
+
const destPath = path.join(resourcesDir, template);
|
|
123
|
+
const srcPath = path.join(skeletonDir, 'templates', template);
|
|
124
|
+
if (skipExisting && fs.existsSync(destPath)) {
|
|
125
|
+
skipped.push(template);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (fs.existsSync(srcPath)) {
|
|
129
|
+
fs.copyFileSync(srcPath, destPath);
|
|
130
|
+
copied.push(template);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { copied, skipped };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Copy root files (CLAUDE.md, AGENTS.md) with project name substitution
|
|
137
|
+
*/
|
|
138
|
+
export function copyRootFiles(targetDir, skeletonDir, projectName, options = {}) {
|
|
139
|
+
const { handleConflicts = false } = options;
|
|
140
|
+
const copied = [];
|
|
141
|
+
const conflicts = [];
|
|
142
|
+
const rootFiles = ['CLAUDE.md', 'AGENTS.md'];
|
|
143
|
+
for (const file of rootFiles) {
|
|
144
|
+
const srcPath = path.join(skeletonDir, 'templates', file);
|
|
145
|
+
const destPath = path.join(targetDir, file);
|
|
146
|
+
if (!fs.existsSync(srcPath)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const content = fs.readFileSync(srcPath, 'utf-8')
|
|
150
|
+
.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
151
|
+
if (fs.existsSync(destPath)) {
|
|
152
|
+
if (handleConflicts) {
|
|
153
|
+
// Create .codev-new for merge
|
|
154
|
+
fs.writeFileSync(destPath + '.codev-new', content);
|
|
155
|
+
conflicts.push(file);
|
|
156
|
+
}
|
|
157
|
+
// Skip if exists and not handling conflicts
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
fs.writeFileSync(destPath, content);
|
|
161
|
+
copied.push(file);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { copied, conflicts };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Create a new .gitignore file with full content (for init)
|
|
168
|
+
*/
|
|
169
|
+
export function createGitignore(targetDir) {
|
|
170
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
171
|
+
fs.writeFileSync(gitignorePath, FULL_GITIGNORE_CONTENT);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Update existing .gitignore or create if not exists (for adopt)
|
|
175
|
+
*/
|
|
176
|
+
export function updateGitignore(targetDir) {
|
|
177
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
178
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
179
|
+
fs.writeFileSync(gitignorePath, CODEV_GITIGNORE_ENTRIES.trim() + '\n');
|
|
180
|
+
return { updated: false, created: true, alreadyPresent: false };
|
|
181
|
+
}
|
|
182
|
+
const existing = fs.readFileSync(gitignorePath, 'utf-8');
|
|
183
|
+
if (existing.includes('.agent-farm/')) {
|
|
184
|
+
return { updated: false, created: false, alreadyPresent: true };
|
|
185
|
+
}
|
|
186
|
+
fs.appendFileSync(gitignorePath, '\n' + CODEV_GITIGNORE_ENTRIES);
|
|
187
|
+
return { updated: true, created: false, alreadyPresent: false };
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/lib/scaffold.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/templates/3d-viewer.html
CHANGED
|
@@ -353,7 +353,7 @@
|
|
|
353
353
|
const loader = new STLLoader();
|
|
354
354
|
|
|
355
355
|
loader.load(
|
|
356
|
-
'
|
|
356
|
+
'api/model',
|
|
357
357
|
(geometry) => {
|
|
358
358
|
// Center geometry
|
|
359
359
|
geometry.computeBoundingBox();
|
|
@@ -396,7 +396,7 @@
|
|
|
396
396
|
const loader = new ThreeMFLoader();
|
|
397
397
|
|
|
398
398
|
loader.load(
|
|
399
|
-
'
|
|
399
|
+
'api/model',
|
|
400
400
|
(group) => {
|
|
401
401
|
// 3MFLoader returns a Group with meshes
|
|
402
402
|
// 3MF uses Z-up, Three.js uses Y-up - rotate to match
|
|
@@ -648,7 +648,7 @@
|
|
|
648
648
|
|
|
649
649
|
async function checkForChanges() {
|
|
650
650
|
try {
|
|
651
|
-
const res = await fetch('
|
|
651
|
+
const res = await fetch('api/mtime');
|
|
652
652
|
if (res.ok) {
|
|
653
653
|
const data = await res.json();
|
|
654
654
|
if (lastMtime === null) {
|
|
@@ -702,7 +702,7 @@
|
|
|
702
702
|
|
|
703
703
|
function reloadSTL() {
|
|
704
704
|
const loader = new STLLoader();
|
|
705
|
-
loader.load('
|
|
705
|
+
loader.load('api/model?t=' + Date.now(), (geometry) => {
|
|
706
706
|
// Center geometry
|
|
707
707
|
geometry.computeBoundingBox();
|
|
708
708
|
const center = new THREE.Vector3();
|
|
@@ -745,7 +745,7 @@
|
|
|
745
745
|
|
|
746
746
|
function reload3MF() {
|
|
747
747
|
const loader = new ThreeMFLoader();
|
|
748
|
-
loader.load('
|
|
748
|
+
loader.load('api/model?t=' + Date.now(), (group) => {
|
|
749
749
|
// Z-up to Y-up rotation
|
|
750
750
|
group.rotation.set(-Math.PI / 2, 0, 0);
|
|
751
751
|
|
|
@@ -51,143 +51,17 @@ async function renderActivityTab() {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// Render activity tab content
|
|
54
|
+
// Uses shared renderActivityContentHtml from utils.js (Maintenance Run 0004)
|
|
54
55
|
function renderActivityTabContent(data) {
|
|
55
56
|
const content = document.getElementById('tab-content');
|
|
56
|
-
|
|
57
|
-
if (data.commits.length === 0 && data.prs.length === 0 && data.builders.length === 0) {
|
|
58
|
-
content.innerHTML = `
|
|
59
|
-
<div class="activity-tab-container">
|
|
60
|
-
<div class="activity-empty">
|
|
61
|
-
<p>No activity recorded today</p>
|
|
62
|
-
<p style="font-size: 12px; margin-top: 8px;">Make some commits or create PRs to see your daily summary!</p>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
`;
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const hours = Math.floor(data.timeTracking.activeMinutes / 60);
|
|
70
|
-
const mins = data.timeTracking.activeMinutes % 60;
|
|
71
|
-
const uniqueBranches = new Set(data.commits.map(c => c.branch)).size;
|
|
72
|
-
const mergedPrs = data.prs.filter(p => p.state === 'MERGED').length;
|
|
73
|
-
|
|
74
|
-
const formatTime = (isoString) => {
|
|
75
|
-
if (!isoString) return '--';
|
|
76
|
-
const date = new Date(isoString);
|
|
77
|
-
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
let html = '<div class="activity-tab-container"><div class="activity-summary">';
|
|
81
|
-
|
|
82
|
-
if (data.aiSummary) {
|
|
83
|
-
html += `<div class="activity-ai-summary">${escapeHtml(data.aiSummary)}</div>`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
html += `
|
|
87
|
-
<div class="activity-section">
|
|
88
|
-
<h4>Activity</h4>
|
|
89
|
-
<ul>
|
|
90
|
-
<li>${data.commits.length} commits across ${uniqueBranches} branch${uniqueBranches !== 1 ? 'es' : ''}</li>
|
|
91
|
-
<li>${data.files.length} files modified</li>
|
|
92
|
-
<li>${data.prs.length} PR${data.prs.length !== 1 ? 's' : ''} created${mergedPrs > 0 ? `, ${mergedPrs} merged` : ''}</li>
|
|
93
|
-
</ul>
|
|
94
|
-
</div>
|
|
95
|
-
`;
|
|
96
|
-
|
|
97
|
-
if (data.projectChanges && data.projectChanges.length > 0) {
|
|
98
|
-
html += `
|
|
99
|
-
<div class="activity-section">
|
|
100
|
-
<h4>Projects Touched</h4>
|
|
101
|
-
<ul>
|
|
102
|
-
${data.projectChanges.map(p => `<li>${escapeHtml(p.id)}: ${escapeHtml(p.title)} (${escapeHtml(p.oldStatus)} → ${escapeHtml(p.newStatus)})</li>`).join('')}
|
|
103
|
-
</ul>
|
|
104
|
-
</div>
|
|
105
|
-
`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
html += `
|
|
109
|
-
<div class="activity-section">
|
|
110
|
-
<h4>Time</h4>
|
|
111
|
-
<p><span class="activity-time-value">~${hours}h ${mins}m</span> active time</p>
|
|
112
|
-
<p>First activity: ${formatTime(data.timeTracking.firstActivity)}</p>
|
|
113
|
-
<p>Last activity: ${formatTime(data.timeTracking.lastActivity)}</p>
|
|
114
|
-
</div>
|
|
115
|
-
`;
|
|
116
|
-
|
|
117
|
-
html += `
|
|
118
|
-
<div class="activity-actions">
|
|
119
|
-
<button class="btn" onclick="copyActivityToClipboard()">Copy to Clipboard</button>
|
|
120
|
-
</div>
|
|
121
|
-
`;
|
|
122
|
-
|
|
123
|
-
html += '</div></div>';
|
|
124
|
-
content.innerHTML = html;
|
|
57
|
+
content.innerHTML = renderActivityContentHtml(data, { isTab: true });
|
|
125
58
|
}
|
|
126
59
|
|
|
127
60
|
// Render activity summary content (for modal)
|
|
61
|
+
// Uses shared renderActivityContentHtml from utils.js (Maintenance Run 0004)
|
|
128
62
|
function renderActivitySummary(data) {
|
|
129
63
|
const content = document.getElementById('activity-content');
|
|
130
|
-
|
|
131
|
-
if (data.commits.length === 0 && data.prs.length === 0 && data.builders.length === 0) {
|
|
132
|
-
content.innerHTML = `
|
|
133
|
-
<div class="activity-empty">
|
|
134
|
-
<p>No activity recorded today</p>
|
|
135
|
-
<p style="font-size: 12px; margin-top: 8px;">Make some commits or create PRs to see your daily summary!</p>
|
|
136
|
-
</div>
|
|
137
|
-
`;
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const hours = Math.floor(data.timeTracking.activeMinutes / 60);
|
|
142
|
-
const mins = data.timeTracking.activeMinutes % 60;
|
|
143
|
-
const uniqueBranches = new Set(data.commits.map(c => c.branch)).size;
|
|
144
|
-
const mergedPrs = data.prs.filter(p => p.state === 'MERGED').length;
|
|
145
|
-
|
|
146
|
-
const formatTime = (isoString) => {
|
|
147
|
-
if (!isoString) return '--';
|
|
148
|
-
const date = new Date(isoString);
|
|
149
|
-
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
let html = '<div class="activity-summary">';
|
|
153
|
-
|
|
154
|
-
if (data.aiSummary) {
|
|
155
|
-
html += `<div class="activity-ai-summary">${escapeHtml(data.aiSummary)}</div>`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
html += `
|
|
159
|
-
<div class="activity-section">
|
|
160
|
-
<h4>Activity</h4>
|
|
161
|
-
<ul>
|
|
162
|
-
<li>${data.commits.length} commits across ${uniqueBranches} branch${uniqueBranches !== 1 ? 'es' : ''}</li>
|
|
163
|
-
<li>${data.files.length} files modified</li>
|
|
164
|
-
<li>${data.prs.length} PR${data.prs.length !== 1 ? 's' : ''} created${mergedPrs > 0 ? `, ${mergedPrs} merged` : ''}</li>
|
|
165
|
-
</ul>
|
|
166
|
-
</div>
|
|
167
|
-
`;
|
|
168
|
-
|
|
169
|
-
if (data.projectChanges && data.projectChanges.length > 0) {
|
|
170
|
-
html += `
|
|
171
|
-
<div class="activity-section">
|
|
172
|
-
<h4>Projects Touched</h4>
|
|
173
|
-
<ul>
|
|
174
|
-
${data.projectChanges.map(p => `<li>${escapeHtml(p.id)}: ${escapeHtml(p.title)} (${escapeHtml(p.oldStatus)} → ${escapeHtml(p.newStatus)})</li>`).join('')}
|
|
175
|
-
</ul>
|
|
176
|
-
</div>
|
|
177
|
-
`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
html += `
|
|
181
|
-
<div class="activity-section">
|
|
182
|
-
<h4>Time</h4>
|
|
183
|
-
<p><span class="activity-time-value">~${hours}h ${mins}m</span> active time</p>
|
|
184
|
-
<p>First activity: ${formatTime(data.timeTracking.firstActivity)}</p>
|
|
185
|
-
<p>Last activity: ${formatTime(data.timeTracking.lastActivity)}</p>
|
|
186
|
-
</div>
|
|
187
|
-
`;
|
|
188
|
-
|
|
189
|
-
html += '</div>';
|
|
190
|
-
content.innerHTML = html;
|
|
64
|
+
content.innerHTML = renderActivityContentHtml(data, { isTab: false });
|
|
191
65
|
}
|
|
192
66
|
|
|
193
67
|
// Close activity modal
|
|
@@ -108,38 +108,9 @@ function hideContextMenu() {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// Handle keyboard navigation in context menu
|
|
111
|
+
// Uses shared handleMenuKeydown from utils.js (Maintenance Run 0004)
|
|
111
112
|
function handleContextMenuKeydown(event) {
|
|
112
|
-
|
|
113
|
-
const items = Array.from(menu.querySelectorAll('.context-menu-item'));
|
|
114
|
-
const currentIndex = items.findIndex(item => item === document.activeElement);
|
|
115
|
-
|
|
116
|
-
switch (event.key) {
|
|
117
|
-
case 'ArrowDown':
|
|
118
|
-
event.preventDefault();
|
|
119
|
-
const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
120
|
-
items[nextIndex].focus();
|
|
121
|
-
break;
|
|
122
|
-
case 'ArrowUp':
|
|
123
|
-
event.preventDefault();
|
|
124
|
-
const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
125
|
-
items[prevIndex].focus();
|
|
126
|
-
break;
|
|
127
|
-
case 'Enter':
|
|
128
|
-
case ' ':
|
|
129
|
-
event.preventDefault();
|
|
130
|
-
const actionName = event.target.dataset.action;
|
|
131
|
-
if (actionName && typeof window[actionName] === 'function') {
|
|
132
|
-
window[actionName]();
|
|
133
|
-
}
|
|
134
|
-
break;
|
|
135
|
-
case 'Escape':
|
|
136
|
-
event.preventDefault();
|
|
137
|
-
hideContextMenu();
|
|
138
|
-
break;
|
|
139
|
-
case 'Tab':
|
|
140
|
-
hideContextMenu();
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
113
|
+
handleMenuKeydown(event, 'context-menu', 'context-menu-item', hideContextMenu);
|
|
143
114
|
}
|
|
144
115
|
|
|
145
116
|
function closeActiveTab() {
|
|
@@ -186,27 +157,11 @@ function setFilePath(path) {
|
|
|
186
157
|
document.getElementById('file-path-input').focus();
|
|
187
158
|
}
|
|
188
159
|
|
|
160
|
+
// Uses shared openFileTab from utils.js (Maintenance Run 0004)
|
|
189
161
|
async function openFile() {
|
|
190
162
|
const path = document.getElementById('file-path-input').value.trim();
|
|
191
163
|
if (!path) return;
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
const response = await fetch('/api/tabs/file', {
|
|
195
|
-
method: 'POST',
|
|
196
|
-
headers: { 'Content-Type': 'application/json' },
|
|
197
|
-
body: JSON.stringify({ path })
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
if (!response.ok) {
|
|
201
|
-
throw new Error(await response.text());
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
hideFileDialog();
|
|
205
|
-
await refresh();
|
|
206
|
-
showToast(`Opened ${path}`, 'success');
|
|
207
|
-
} catch (err) {
|
|
208
|
-
showToast('Failed to open file: ' + err.message, 'error');
|
|
209
|
-
}
|
|
164
|
+
await openFileTab(path, { onSuccess: hideFileDialog });
|
|
210
165
|
}
|
|
211
166
|
|
|
212
167
|
// Spawn worktree builder
|
|
@@ -181,36 +181,9 @@ async function refreshFilesTree() {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// Open file from tree click
|
|
184
|
+
// Uses shared openFileTab from utils.js (Maintenance Run 0004)
|
|
184
185
|
async function openFileFromTree(filePath) {
|
|
185
|
-
|
|
186
|
-
const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
187
|
-
if (existingTab) {
|
|
188
|
-
selectTab(existingTab.id);
|
|
189
|
-
refreshFileTab(existingTab.id);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const response = await fetch('/api/tabs/file', {
|
|
194
|
-
method: 'POST',
|
|
195
|
-
headers: { 'Content-Type': 'application/json' },
|
|
196
|
-
body: JSON.stringify({ path: filePath })
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
if (!response.ok) {
|
|
200
|
-
throw new Error(await response.text());
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
await refresh();
|
|
204
|
-
|
|
205
|
-
const newTab = tabs.find(t => t.type === 'file' && t.path === filePath);
|
|
206
|
-
if (newTab) {
|
|
207
|
-
selectTab(newTab.id);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
showToast(`Opened ${getFileName(filePath)}`, 'success');
|
|
211
|
-
} catch (err) {
|
|
212
|
-
showToast('Failed to open file: ' + err.message, 'error');
|
|
213
|
-
}
|
|
186
|
+
await openFileTab(filePath, { showSwitchToast: false });
|
|
214
187
|
}
|
|
215
188
|
|
|
216
189
|
// ========================================
|