code-workspace-zhuiyi 0.1.0-beta.1
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/.env.example +3 -0
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/README.zh-CN.md +168 -0
- package/artifacts/manifest.json +183 -0
- package/artifacts/templates/USER_GUIDE.template.md +1 -0
- package/artifacts/templates/agents/WORKSPACE_GUARD.md.template +73 -0
- package/artifacts/templates/agents/skills/code-workspace-resolve-branch/SKILL.md +88 -0
- package/artifacts/templates/agents/skills/code-workspace-resolve-branch/evals/evals.json +45 -0
- package/artifacts/templates/claude/commands/code-workspace/add-projects.md +23 -0
- package/artifacts/templates/codex/hooks.json +86 -0
- package/artifacts/templates/codex/skills/code-workspace-add-projects/SKILL.md +28 -0
- package/artifacts/templates/user-guide/en-US.md +97 -0
- package/artifacts/templates/user-guide/zh-CN.md +97 -0
- package/assets/i18n-icon.svg +1 -0
- package/assets/logo-vector.svg +30 -0
- package/assets/request_tip.mp3 +0 -0
- package/assets/session_finish.mp3 +0 -0
- package/bin/code-workspace.js +11 -0
- package/docs/extension-architecture.zh-CN.md +429 -0
- package/docs/extensions.md +64 -0
- package/docs/extensions.zh-CN.md +64 -0
- package/extensions/openspec-workspace/1.0.0/artifacts/claude/SKILL.md +16 -0
- package/extensions/openspec-workspace/1.0.0/artifacts/codex/SKILL.md +16 -0
- package/extensions/openspec-workspace/1.0.0/init.js +54 -0
- package/extensions/openspec-workspace/1.0.0/manifest.json +27 -0
- package/extensions/zhuiyi-jira-mcp/0.1.0/artifacts/claude/server.json +14 -0
- package/extensions/zhuiyi-jira-mcp/0.1.0/artifacts/codex/config.toml +11 -0
- package/extensions/zhuiyi-jira-mcp/0.1.0/init.js +53 -0
- package/extensions/zhuiyi-jira-mcp/0.1.0/lib/archive.js +196 -0
- package/extensions/zhuiyi-jira-mcp/0.1.0/manifest.json +38 -0
- package/extensions/zhuiyi-jira-mcp/0.1.0/release.json +15 -0
- package/package.json +58 -0
- package/schemas/extension-init-context-v1.json +39 -0
- package/schemas/extension-init-result-v1.json +42 -0
- package/schemas/extension-manifest-v1.json +55 -0
- package/schemas/extension-manifest-v2.json +84 -0
- package/schemas/extension-manifest-v3.json +84 -0
- package/spec/extension/v1/specification.en-US.md +171 -0
- package/spec/extension/v1/specification.zh-CN.md +170 -0
- package/src/cli/commands/completion.js +203 -0
- package/src/cli/commands/extension.js +193 -0
- package/src/cli/commands/help.js +36 -0
- package/src/cli/commands/init.js +200 -0
- package/src/cli/commands/monitor.js +62 -0
- package/src/cli/commands/permissions.js +33 -0
- package/src/cli/commands/project-branch-update.js +107 -0
- package/src/cli/commands/project-branch.js +433 -0
- package/src/cli/commands/project.js +259 -0
- package/src/cli/commands/update.js +146 -0
- package/src/cli/commands/workspace.js +24 -0
- package/src/cli/confirmation.js +22 -0
- package/src/cli/parser.js +108 -0
- package/src/cli/registry.js +108 -0
- package/src/cli/renderer.js +20 -0
- package/src/cli/result.js +118 -0
- package/src/cli.js +76 -0
- package/src/core/assets.js +83 -0
- package/src/core/config.js +378 -0
- package/src/core/diagnostics.js +20 -0
- package/src/core/directory-digest.js +34 -0
- package/src/core/doctor.js +115 -0
- package/src/core/errors.js +10 -0
- package/src/core/extension-artifacts-legacy.js +180 -0
- package/src/core/extension-artifacts.js +310 -0
- package/src/core/extensions.js +1216 -0
- package/src/core/fs.js +32 -0
- package/src/core/init-lock-holder.js +30 -0
- package/src/core/init-lock.js +159 -0
- package/src/core/init.js +160 -0
- package/src/core/initializer.js +233 -0
- package/src/core/language.js +102 -0
- package/src/core/managed-files.js +334 -0
- package/src/core/migration.js +68 -0
- package/src/core/permissions/claude.js +92 -0
- package/src/core/permissions/codex.js +126 -0
- package/src/core/permissions/common.js +44 -0
- package/src/core/permissions/index.js +163 -0
- package/src/core/project-branch-update.js +424 -0
- package/src/core/project-configuration.js +55 -0
- package/src/core/project.js +674 -0
- package/src/core/tools.js +34 -0
- package/src/core/transaction.js +132 -0
- package/src/core/validation.js +186 -0
- package/src/i18n/index.js +11 -0
- package/src/i18n/interpolate.js +7 -0
- package/src/i18n/locales/en-US.js +10 -0
- package/src/i18n/locales/zh-CN.js +11 -0
- package/src/i18n/registry.js +42 -0
- package/src/index.js +25 -0
- package/src/init/plan.js +12 -0
- package/src/init/ui.js +61 -0
- package/src/init/wizard.js +97 -0
- package/src/monitor/i18n/index.js +33 -0
- package/src/monitor/i18n/locales/en-US.js +85 -0
- package/src/monitor/i18n/locales/zh-CN.js +79 -0
- package/src/monitor/index.js +344 -0
- package/src/monitor/page.js +118 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
|
|
3
|
+
const { cleanupObsoleteAssets, planObsoleteAssets } = require("../../core/assets");
|
|
4
|
+
const {
|
|
5
|
+
configPath,
|
|
6
|
+
loadConfig,
|
|
7
|
+
loadState,
|
|
8
|
+
saveConfig,
|
|
9
|
+
statePath,
|
|
10
|
+
} = require("../../core/config");
|
|
11
|
+
const { WorkspaceError } = require("../../core/errors");
|
|
12
|
+
const { commitUpdateState, loadInitManifest } = require("../../core/init");
|
|
13
|
+
const { workspaceGuide } = require("../../core/language");
|
|
14
|
+
const { inspectManagedFiles, installManagedFiles, planManagedFiles } = require("../../core/managed-files");
|
|
15
|
+
const { planWorkspaceMaintenance } = require("../../core/migration");
|
|
16
|
+
const { inspectProjectPermissions } = require("../../core/permissions");
|
|
17
|
+
const { resolveWorkspaceTools } = require("../../core/tools");
|
|
18
|
+
const { createFileTransaction } = require("../../core/transaction");
|
|
19
|
+
const { success } = require("../result");
|
|
20
|
+
const { migrationData } = require("./init");
|
|
21
|
+
|
|
22
|
+
function verifyUpdatedManagedFiles(root, manifest, tools, capabilities, variables) {
|
|
23
|
+
const inspection = inspectManagedFiles(root, manifest, tools, capabilities, variables);
|
|
24
|
+
const incomplete = [
|
|
25
|
+
...inspection.managedOld.map((target) => ({ target, state: "managed-old" })),
|
|
26
|
+
...inspection.replaceable.map((target) => ({ target, state: "replaceable" })),
|
|
27
|
+
...inspection.missing.map((target) => ({ target, state: "missing" })),
|
|
28
|
+
...inspection.unknown.map((target) => ({ target, state: "unknown" })),
|
|
29
|
+
];
|
|
30
|
+
if (incomplete.length > 0) {
|
|
31
|
+
throw new WorkspaceError(
|
|
32
|
+
"UPDATE_POSTCONDITION_FAILED",
|
|
33
|
+
`Updated managed files failed verification: ${incomplete.map((entry) => `${entry.target} (${entry.state})`).join(", ")}`,
|
|
34
|
+
{ files: incomplete, remediation: "Run code-w doctor --json, review the reported files, and retry update." }
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return inspection;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function updateWorkspace(root, options = {}) {
|
|
41
|
+
const manifest = loadInitManifest();
|
|
42
|
+
const initialState = loadState(root);
|
|
43
|
+
if (!initialState) {
|
|
44
|
+
throw new WorkspaceError(
|
|
45
|
+
"UPDATE_STATE_MISSING",
|
|
46
|
+
"Local initialization state is missing; update can only run on an initialized workspace.",
|
|
47
|
+
{ file: statePath(root), remediation: "Re-run code-w init . --yes before updating this workspace." }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const toolSelection = resolveWorkspaceTools({
|
|
51
|
+
explicit: options.tools,
|
|
52
|
+
state: initialState,
|
|
53
|
+
manifestTools: manifest.tools,
|
|
54
|
+
});
|
|
55
|
+
const tools = toolSelection.tools;
|
|
56
|
+
const migration = planWorkspaceMaintenance(root, {
|
|
57
|
+
language: options.language,
|
|
58
|
+
allowLegacy: true,
|
|
59
|
+
defaultLanguage: false,
|
|
60
|
+
});
|
|
61
|
+
const language = migration.language.value;
|
|
62
|
+
const config = loadConfig(root, { defaultLanguage: language });
|
|
63
|
+
const nextConfig = { ...config, workspace: { ...config.workspace, language } };
|
|
64
|
+
const capabilities = nextConfig.monitor.enable ? ["monitor"] : [];
|
|
65
|
+
const variables = { WORKSPACE_LANGUAGE: language, WORKSPACE_USER_GUIDE: workspaceGuide(language) };
|
|
66
|
+
let managedPlan;
|
|
67
|
+
let obsoletePlan;
|
|
68
|
+
try {
|
|
69
|
+
managedPlan = planManagedFiles(root, manifest, tools, {
|
|
70
|
+
force: options.force === true,
|
|
71
|
+
capabilities,
|
|
72
|
+
variables,
|
|
73
|
+
});
|
|
74
|
+
obsoletePlan = planObsoleteAssets(root, tools, { force: options.force === true });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (options.language && /unknown changes/.test(error.message)) {
|
|
77
|
+
throw new WorkspaceError("MANAGED_FILE_UNKNOWN", `${error.message}. No configuration or artifacts were changed.`, {
|
|
78
|
+
remediation: `Review the file or re-run code-w update --language ${language} --force.`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
const transaction = createFileTransaction([
|
|
84
|
+
configPath(root),
|
|
85
|
+
statePath(root),
|
|
86
|
+
...managedPlan.plans.map((plan) => plan.target),
|
|
87
|
+
...obsoletePlan.map((plan) => path.join(root, plan.target)),
|
|
88
|
+
]);
|
|
89
|
+
try {
|
|
90
|
+
const obsoleteFiles = cleanupObsoleteAssets(root, tools, { force: options.force === true });
|
|
91
|
+
options.injectFailure?.("after-obsolete-cleanup", obsoleteFiles);
|
|
92
|
+
const managedFiles = installManagedFiles(root, manifest, tools, {
|
|
93
|
+
force: options.force === true,
|
|
94
|
+
capabilities,
|
|
95
|
+
variables,
|
|
96
|
+
});
|
|
97
|
+
options.injectFailure?.("after-managed-install", managedFiles);
|
|
98
|
+
saveConfig(root, nextConfig);
|
|
99
|
+
options.injectFailure?.("after-config-save", nextConfig);
|
|
100
|
+
verifyUpdatedManagedFiles(root, manifest, tools, capabilities, variables);
|
|
101
|
+
const result = {
|
|
102
|
+
language,
|
|
103
|
+
tools: toolSelection,
|
|
104
|
+
migration: migrationData(migration),
|
|
105
|
+
obsoleteFiles,
|
|
106
|
+
managedFiles,
|
|
107
|
+
forcedUnknown: managedPlan.plans.filter((plan) => plan.reason === "unknown").map((plan) => plan.entry.target),
|
|
108
|
+
diagnostics: [],
|
|
109
|
+
};
|
|
110
|
+
const previousTools = new Set(initialState.tools || []);
|
|
111
|
+
const addedTools = tools.filter((tool) => !previousTools.has(tool));
|
|
112
|
+
if (addedTools.length > 0 && nextConfig.projects.length > 0) {
|
|
113
|
+
for (const inspection of inspectProjectPermissions({ root, tools: addedTools, projects: nextConfig.projects })) {
|
|
114
|
+
if (inspection.missing.length === 0) continue;
|
|
115
|
+
result.diagnostics.push({
|
|
116
|
+
code: "WORKSPACE_PERMISSION_APPLY_REQUIRED",
|
|
117
|
+
severity: "warning",
|
|
118
|
+
message: `${inspection.tool} is selected but lacks authorization for ${inspection.missing.length} registered project director${inspection.missing.length === 1 ? "y" : "ies"}.`,
|
|
119
|
+
tool: inspection.tool,
|
|
120
|
+
directories: inspection.missing,
|
|
121
|
+
file: inspection.target,
|
|
122
|
+
remediation: "Review and run code-w permissions apply --yes.",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
options.injectFailure?.("after-verify", result);
|
|
127
|
+
const state = commitUpdateState(root, manifest, {
|
|
128
|
+
tools,
|
|
129
|
+
removeLegacyWorkspaceLanguage: true,
|
|
130
|
+
});
|
|
131
|
+
options.injectFailure?.("after-state-save", state);
|
|
132
|
+
transaction.commit();
|
|
133
|
+
return result;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
transaction.rollback(error);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function executeUpdate(invocation) {
|
|
141
|
+
const result = updateWorkspace(invocation.root, invocation.options);
|
|
142
|
+
const written = result.managedFiles.filter((entry) => entry.action === "write").length;
|
|
143
|
+
return success("update", result, `Updated Code Workspace assets (${written} written). Tools: ${result.tools.tools.join(", ") || "none"} (${result.tools.source}).`, result.diagnostics);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = { executeUpdate, updateWorkspace };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { loadInitManifest } = require("../../core/init");
|
|
2
|
+
const { doctorWorkspace } = require("../../core/doctor");
|
|
3
|
+
const { getLocale } = require("../../i18n");
|
|
4
|
+
const { fromDiagnostics, success } = require("../result");
|
|
5
|
+
|
|
6
|
+
function executeLanguage(invocation) {
|
|
7
|
+
const language = invocation.config.workspace.language;
|
|
8
|
+
const locale = getLocale(language);
|
|
9
|
+
return success("language", { language, label: locale.label, projectContext: locale.projectContext }, language);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function executeDoctor(invocation) {
|
|
13
|
+
const output = doctorWorkspace(invocation.root, loadInitManifest());
|
|
14
|
+
return fromDiagnostics("doctor", output, {
|
|
15
|
+
projects: output.config.projects.length,
|
|
16
|
+
tools: output.toolSelection,
|
|
17
|
+
capabilities: output.capabilities,
|
|
18
|
+
}, `Code Workspace is healthy (${output.config.projects.length} local projects).`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
executeDoctor,
|
|
23
|
+
executeLanguage,
|
|
24
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const readline = require("node:readline/promises");
|
|
2
|
+
|
|
3
|
+
const { WorkspaceError } = require("../core/errors");
|
|
4
|
+
|
|
5
|
+
async function confirm(message, options = {}) {
|
|
6
|
+
if (options.yes === true) return true;
|
|
7
|
+
if (options.json || !process.stdin.isTTY) {
|
|
8
|
+
throw new WorkspaceError(
|
|
9
|
+
"CLI_CONFIRMATION_REQUIRED",
|
|
10
|
+
`${message} Re-run with --yes to confirm non-interactively.`,
|
|
11
|
+
{ remediation: "Re-run the command with --yes." }
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
const input = readline.createInterface({ input: options.input || process.stdin, output: options.output || process.stdout });
|
|
15
|
+
try {
|
|
16
|
+
return /^y(?:es)?$/i.test((await input.question(`${message} [y/N] `)).trim());
|
|
17
|
+
} finally {
|
|
18
|
+
input.close();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { confirm };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const { WorkspaceError } = require("../core/errors");
|
|
2
|
+
const { COMMANDS, GLOBAL_OPTIONS, getCommand } = require("./registry");
|
|
3
|
+
|
|
4
|
+
const aliasEntries = Object.entries(GLOBAL_OPTIONS).flatMap(([name, definition]) =>
|
|
5
|
+
(definition.aliases || []).map((alias) => [alias, name])
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
function optionName(token) {
|
|
9
|
+
if (token === "-p") return "p";
|
|
10
|
+
if (!token.startsWith("--") || token === "--") return null;
|
|
11
|
+
return token.slice(2).split("=", 1)[0];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function commandCandidates(first) {
|
|
15
|
+
return COMMANDS.filter((command) => command.path[0] === first).sort((left, right) => right.path.length - left.path.length);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolveCommand(tokens) {
|
|
19
|
+
let index = 0;
|
|
20
|
+
const leadingOptions = {};
|
|
21
|
+
while (index < tokens.length && optionName(tokens[index])) {
|
|
22
|
+
const token = tokens[index];
|
|
23
|
+
const rawName = optionName(token);
|
|
24
|
+
const name = new Map(aliasEntries).get(rawName) || rawName;
|
|
25
|
+
const definition = GLOBAL_OPTIONS[name];
|
|
26
|
+
if (!definition) throw new WorkspaceError("CLI_UNKNOWN_OPTION", `Unknown global option: ${token.split("=")[0]}`, { option: rawName });
|
|
27
|
+
if (token.includes("=")) throw new WorkspaceError("CLI_INVALID_OPTION_VALUE", `Boolean option --${name} does not accept a value`, { option: name });
|
|
28
|
+
leadingOptions[name] = true;
|
|
29
|
+
index += 1;
|
|
30
|
+
}
|
|
31
|
+
if (index >= tokens.length) return { command: null, index, leadingOptions };
|
|
32
|
+
const candidates = commandCandidates(tokens[index]);
|
|
33
|
+
const command = candidates.find((candidate) => candidate.path.every((part, offset) => tokens[index + offset] === part));
|
|
34
|
+
if (!command) throw new WorkspaceError("CLI_UNKNOWN_COMMAND", `Unknown command: ${tokens[index]}`, { command: tokens[index] });
|
|
35
|
+
return { command, index: index + command.path.length, leadingOptions };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseOption(token, definitions, tokens, index) {
|
|
39
|
+
const rawName = optionName(token);
|
|
40
|
+
const aliases = Object.entries(definitions).flatMap(([name, definition]) => (definition.aliases || []).map((alias) => [alias, name]));
|
|
41
|
+
const name = new Map(aliases).get(rawName) || rawName;
|
|
42
|
+
const definition = definitions[name];
|
|
43
|
+
if (!definition) throw new WorkspaceError("CLI_UNKNOWN_OPTION", `Unknown option for this command: ${token.split("=")[0]}`, { option: rawName });
|
|
44
|
+
const equal = token.indexOf("=");
|
|
45
|
+
if (definition.type === "boolean") {
|
|
46
|
+
if (equal >= 0) throw new WorkspaceError("CLI_INVALID_OPTION_VALUE", `Boolean option --${name} does not accept a value`, { option: name });
|
|
47
|
+
return { name, value: true, next: index + 1 };
|
|
48
|
+
}
|
|
49
|
+
const value = equal >= 0 ? token.slice(equal + 1) : tokens[index + 1];
|
|
50
|
+
if (!value || (equal < 0 && value.startsWith("-"))) {
|
|
51
|
+
throw new WorkspaceError("CLI_OPTION_VALUE_REQUIRED", `--${name} requires a value`, { option: name });
|
|
52
|
+
}
|
|
53
|
+
return { name, value, next: equal >= 0 ? index + 1 : index + 2 };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parse(argv) {
|
|
57
|
+
const tokens = argv.slice(2);
|
|
58
|
+
const resolved = resolveCommand(tokens);
|
|
59
|
+
if (!resolved.command) return { command: null, positionals: [], args: [], options: resolved.leadingOptions };
|
|
60
|
+
try {
|
|
61
|
+
const definitions = { ...GLOBAL_OPTIONS, ...resolved.command.options };
|
|
62
|
+
const options = { ...resolved.leadingOptions };
|
|
63
|
+
const args = [];
|
|
64
|
+
let positionalOnly = false;
|
|
65
|
+
for (let index = resolved.index; index < tokens.length;) {
|
|
66
|
+
const token = tokens[index];
|
|
67
|
+
if (token === "--") {
|
|
68
|
+
positionalOnly = true;
|
|
69
|
+
index += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!positionalOnly && token.startsWith("-")) {
|
|
73
|
+
const parsed = parseOption(token, definitions, tokens, index);
|
|
74
|
+
if (Object.prototype.hasOwnProperty.call(options, parsed.name)) {
|
|
75
|
+
throw new WorkspaceError("CLI_DUPLICATE_OPTION", `Option provided more than once: --${parsed.name}`, { option: parsed.name });
|
|
76
|
+
}
|
|
77
|
+
options[parsed.name] = parsed.value;
|
|
78
|
+
index = parsed.next;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
args.push(token);
|
|
82
|
+
index += 1;
|
|
83
|
+
}
|
|
84
|
+
const required = resolved.command.args.filter((argument) => argument.required).length;
|
|
85
|
+
if (!options.help && args.length < required) {
|
|
86
|
+
const missing = resolved.command.args[args.length]?.name || "argument";
|
|
87
|
+
throw new WorkspaceError("CLI_ARGUMENT_REQUIRED", `${resolved.command.path.join(" ")} requires <${missing}>`, { argument: missing });
|
|
88
|
+
}
|
|
89
|
+
const variadic = resolved.command.args.at(-1)?.variadic === true;
|
|
90
|
+
if (!options.help && !variadic && args.length > resolved.command.args.length) {
|
|
91
|
+
throw new WorkspaceError("CLI_EXTRA_ARGUMENT", `Unexpected argument for ${resolved.command.path.join(" ")}: ${args[resolved.command.args.length]}`, { argument: args[resolved.command.args.length] });
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
command: resolved.command,
|
|
95
|
+
args,
|
|
96
|
+
options,
|
|
97
|
+
positionals: [...resolved.command.path, ...args],
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
error.details = {
|
|
101
|
+
...(error.details || {}),
|
|
102
|
+
command: resolved.command.path.join("."),
|
|
103
|
+
};
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { parse, resolveCommand };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const GLOBAL_OPTIONS = {
|
|
2
|
+
help: { type: "boolean" },
|
|
3
|
+
version: { type: "boolean" },
|
|
4
|
+
json: { type: "boolean" },
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const COMMAND_SUMMARIES = {
|
|
8
|
+
init: "Initialize workspace (path defaults to current directory)",
|
|
9
|
+
monitor: "Run the global multi-workspace Agent monitor",
|
|
10
|
+
update: "Update Workspace-owned managed assets",
|
|
11
|
+
language: "Print the workspace language",
|
|
12
|
+
"project inspect": "Inspect a Git project without writing files",
|
|
13
|
+
"project add": "Register a project (low-level skill command)",
|
|
14
|
+
"project remove": "Remove a local project",
|
|
15
|
+
"project branch inspect": "Inspect selected projects' registered and actual branches",
|
|
16
|
+
"project branch verify": "Verify selected projects' registered and actual branches match",
|
|
17
|
+
"project branch accept-actual": "Update selected registered branches from actual branches",
|
|
18
|
+
"project branch use-registered": "Switch selected project worktrees to registered branches",
|
|
19
|
+
"project branch update-latest": "Update selected enabled project branches to their latest upstream commits",
|
|
20
|
+
"project list": "List local projects",
|
|
21
|
+
"project show": "Show one local project",
|
|
22
|
+
"project verify": "Validate all local projects or selected projects",
|
|
23
|
+
"permissions apply": "Apply registered project directory authorization",
|
|
24
|
+
"extension install": "Install one or more built-in extensions into the Workspace",
|
|
25
|
+
"extension uninstall": "Uninstall one built-in extension from the Workspace",
|
|
26
|
+
doctor: "Report workspace health",
|
|
27
|
+
completion: "Print shell completion script",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const COMMANDS = [
|
|
31
|
+
{ path: ["init"], args: [{ name: "path", required: false }], workspace: "target", config: [], interaction: "optional", effects: "planned-write", options: {
|
|
32
|
+
tools: { type: "string" }, extensions: { type: "string" }, "workspace-name": { type: "string" }, language: { type: "string" },
|
|
33
|
+
monitor: { type: "boolean" }, "no-monitor": { type: "boolean" }, "monitor-url": { type: "string" }, yes: { type: "boolean" }, force: { type: "boolean" },
|
|
34
|
+
} },
|
|
35
|
+
{ path: ["monitor"], args: [], workspace: "none", config: [], interaction: "never", effects: "external", options: { port: { type: "string", aliases: ["p"] } } },
|
|
36
|
+
{ path: ["monitor", "report"], args: [], workspace: "optional", config: ["identity", "monitor"], interaction: "never", effects: "external", options: {} },
|
|
37
|
+
{ path: ["update"], args: [], workspace: "required", config: [], interaction: "optional", effects: "planned-write", options: {
|
|
38
|
+
tools: { type: "string" }, language: { type: "string" }, force: { type: "boolean" },
|
|
39
|
+
} },
|
|
40
|
+
{ path: ["language"], args: [], workspace: "required", config: ["language"], interaction: "never", effects: "read-only", options: {} },
|
|
41
|
+
{ path: ["project", "inspect"], args: [{ name: "path", required: true }], workspace: "none", config: [], interaction: "never", effects: "read-only", options: {} },
|
|
42
|
+
{ path: ["project", "add"], args: [{ name: "path", required: false }], workspace: "required", config: ["complete"], interaction: "required", effects: "planned-write", options: {
|
|
43
|
+
"project-file": { type: "string" }, "projects-file": { type: "string" }, name: { type: "string" }, type: { type: "string" },
|
|
44
|
+
context: { type: "string" }, "context-file": { type: "string" }, yes: { type: "boolean" },
|
|
45
|
+
} },
|
|
46
|
+
{ path: ["project", "remove"], args: [{ name: "name", required: true }], workspace: "required", config: ["complete"], interaction: "required", effects: "planned-write", options: { yes: { type: "boolean" } } },
|
|
47
|
+
{ path: ["project", "branch", "inspect"], args: [{ name: "name", required: true, variadic: true }], workspace: "required", config: ["projects"], interaction: "never", effects: "read-only", options: {} },
|
|
48
|
+
{ path: ["project", "branch", "verify"], args: [{ name: "name", required: true, variadic: true }], workspace: "required", config: ["projects"], interaction: "never", effects: "read-only", options: {} },
|
|
49
|
+
{ path: ["project", "branch", "accept-actual"], args: [{ name: "name", required: true, variadic: true }], workspace: "required", config: ["projects"], interaction: "required", effects: "planned-write", options: { yes: { type: "boolean" } } },
|
|
50
|
+
{ path: ["project", "branch", "use-registered"], args: [{ name: "name", required: true, variadic: true }], workspace: "required", config: ["projects"], interaction: "required", effects: "external", options: { yes: { type: "boolean" }, "allow-remote": { type: "boolean" }, remote: { type: "string" } } },
|
|
51
|
+
{ path: ["project", "branch", "update-latest"], args: [{ name: "name", required: true, variadic: true }], workspace: "required", config: ["projects"], interaction: "never", effects: "external", options: {} },
|
|
52
|
+
{ path: ["project", "list"], args: [], workspace: "required", config: ["projects"], interaction: "never", effects: "read-only", options: {} },
|
|
53
|
+
{ path: ["project", "show"], args: [{ name: "name", required: false }], workspace: "required", config: ["projects"], interaction: "never", effects: "read-only", options: { name: { type: "string" } } },
|
|
54
|
+
{ path: ["project", "verify"], args: [{ name: "name", required: false, variadic: true }], workspace: "required", config: ["projects"], interaction: "never", effects: "read-only", options: {} },
|
|
55
|
+
{ path: ["permissions", "apply"], args: [], workspace: "required", config: ["projects"], interaction: "required", effects: "planned-write", options: {
|
|
56
|
+
tools: { type: "string" }, yes: { type: "boolean" },
|
|
57
|
+
} },
|
|
58
|
+
{ path: ["extension", "install"], args: [{ name: "name", required: false, variadic: true }], workspace: "required", config: ["identity", "language"], interaction: "required", effects: "planned-write", options: { yes: { type: "boolean" } } },
|
|
59
|
+
{ path: ["extension", "uninstall"], args: [{ name: "name", required: true }], workspace: "required", config: [], interaction: "required", effects: "planned-write", options: { yes: { type: "boolean" } } },
|
|
60
|
+
{ path: ["doctor"], args: [], workspace: "required", config: ["complete"], interaction: "never", effects: "read-only", options: {} },
|
|
61
|
+
{ path: ["completion"], args: [], workspace: "none", config: [], interaction: "never", effects: "read-only", options: { shell: { type: "string" } } },
|
|
62
|
+
{ path: ["help"], args: [], workspace: "none", config: [], interaction: "never", effects: "read-only", options: {} },
|
|
63
|
+
{ path: ["version"], args: [], workspace: "none", config: [], interaction: "never", effects: "read-only", options: {} },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const byPath = new Map(COMMANDS.map((command) => [command.path.join(" "), Object.freeze(command)]));
|
|
67
|
+
|
|
68
|
+
function getCommand(path) {
|
|
69
|
+
return byPath.get(Array.isArray(path) ? path.join(" ") : String(path || "")) || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function commandHelpRows() {
|
|
73
|
+
return COMMANDS
|
|
74
|
+
.filter((command) => COMMAND_SUMMARIES[command.path.join(" ")])
|
|
75
|
+
.map((command) => ({
|
|
76
|
+
usage: [
|
|
77
|
+
...command.path,
|
|
78
|
+
...command.args.map((argument) => {
|
|
79
|
+
const name = `${argument.name}${argument.variadic ? "..." : ""}`;
|
|
80
|
+
return argument.required ? `<${name}>` : `[${name}]`;
|
|
81
|
+
}),
|
|
82
|
+
].join(" "),
|
|
83
|
+
summary: COMMAND_SUMMARIES[command.path.join(" ")],
|
|
84
|
+
command,
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function validateCommandReference(reference) {
|
|
89
|
+
const tokens = [];
|
|
90
|
+
const input = String(reference || "").trim();
|
|
91
|
+
const pattern = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([^\s]+)/g;
|
|
92
|
+
for (const match of input.matchAll(pattern)) tokens.push(match[1] ?? match[2] ?? match[3]);
|
|
93
|
+
if (tokens.length === 0) return { valid: false, reason: "empty command reference" };
|
|
94
|
+
const normalized = tokens.map((token) => /^<[^>]+>$/.test(token) ? "placeholder" : token);
|
|
95
|
+
try {
|
|
96
|
+
const parsed = require("./parser").parse([process.execPath, "code-workspace", ...normalized]);
|
|
97
|
+
return {
|
|
98
|
+
valid: true,
|
|
99
|
+
command: parsed.command,
|
|
100
|
+
options: Object.keys(parsed.options),
|
|
101
|
+
args: parsed.args,
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return { valid: false, reason: `${error.code || "CLI_ERROR"}: ${error.message}` };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { COMMANDS: Object.freeze(COMMANDS), GLOBAL_OPTIONS: Object.freeze(GLOBAL_OPTIONS), commandHelpRows, getCommand, validateCommandReference };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { jsonEnvelope } = require("./result");
|
|
2
|
+
|
|
3
|
+
function renderResult(result, options = {}) {
|
|
4
|
+
const stdout = options.stdout || process.stdout;
|
|
5
|
+
const stderr = options.stderr || process.stderr;
|
|
6
|
+
if (options.json) {
|
|
7
|
+
stdout.write(`${JSON.stringify(jsonEnvelope(result), null, 2)}\n`);
|
|
8
|
+
} else {
|
|
9
|
+
for (const diagnostic of result.diagnostics) {
|
|
10
|
+
const stream = diagnostic.severity === "warning" ? stderr : diagnostic.severity === "error" ? stderr : stdout;
|
|
11
|
+
stream.write(`${diagnostic.severity === "warning" ? "WARN" : diagnostic.severity === "error" ? "ERROR" : "INFO"} ${diagnostic.message}\n`);
|
|
12
|
+
if (diagnostic.remediation) stream.write(`REMEDIATION ${diagnostic.remediation}\n`);
|
|
13
|
+
}
|
|
14
|
+
if (result.text && (result.ok || result.renderTextOnError)) stdout.write(result.text.endsWith("\n") ? result.text : `${result.text}\n`);
|
|
15
|
+
}
|
|
16
|
+
if (!result.ok) process.exitCode = 1;
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { renderResult };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const { WorkspaceError } = require("../core/errors");
|
|
2
|
+
|
|
3
|
+
const RESULT_SCHEMA_VERSION = 1;
|
|
4
|
+
|
|
5
|
+
function commandName(command) {
|
|
6
|
+
if (Array.isArray(command)) return command.join(".");
|
|
7
|
+
return command ? String(command) : null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalizeDiagnostic(entry) {
|
|
11
|
+
if (!entry || typeof entry !== "object") {
|
|
12
|
+
return { code: "CLI_DIAGNOSTIC_INVALID", severity: "error", message: String(entry) };
|
|
13
|
+
}
|
|
14
|
+
const { code, severity, message, details, ...rest } = entry;
|
|
15
|
+
return {
|
|
16
|
+
code: code || "CLI_ERROR",
|
|
17
|
+
severity: severity || "error",
|
|
18
|
+
message: message || "Unknown command diagnostic",
|
|
19
|
+
...rest,
|
|
20
|
+
...(details && typeof details === "object" ? details : {}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function commandResult(command, data = null, options = {}) {
|
|
25
|
+
const diagnostics = (options.diagnostics || []).map(normalizeDiagnostic);
|
|
26
|
+
const ok = options.ok ?? !diagnostics.some((entry) => entry.severity === "error");
|
|
27
|
+
return {
|
|
28
|
+
schemaVersion: RESULT_SCHEMA_VERSION,
|
|
29
|
+
ok,
|
|
30
|
+
command: commandName(command),
|
|
31
|
+
data: data ?? null,
|
|
32
|
+
diagnostics,
|
|
33
|
+
text: options.text || "",
|
|
34
|
+
renderTextOnError: options.renderTextOnError === true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function success(command, data = null, text = "", diagnostics = []) {
|
|
39
|
+
return commandResult(command, data, { ok: true, text, diagnostics });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function fromDiagnostics(command, output, data = null, text = "") {
|
|
43
|
+
return commandResult(command, data, {
|
|
44
|
+
ok: !output.diagnostics.some((entry) => entry.severity === "error"),
|
|
45
|
+
diagnostics: output.diagnostics,
|
|
46
|
+
text,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function failure(error, command = null) {
|
|
51
|
+
const source = error instanceof Error ? error : new WorkspaceError("CLI_ERROR", String(error));
|
|
52
|
+
return commandResult(command, null, {
|
|
53
|
+
ok: false,
|
|
54
|
+
diagnostics: [{
|
|
55
|
+
code: source.code || "CLI_ERROR",
|
|
56
|
+
severity: "error",
|
|
57
|
+
message: source.message,
|
|
58
|
+
details: source.details,
|
|
59
|
+
}],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function diagnosticFromError(error, details = {}) {
|
|
64
|
+
const source = error instanceof Error ? error : new WorkspaceError("CLI_ERROR", String(error));
|
|
65
|
+
return normalizeDiagnostic({
|
|
66
|
+
code: source.code || "CLI_ERROR",
|
|
67
|
+
severity: "error",
|
|
68
|
+
message: source.message,
|
|
69
|
+
details: {
|
|
70
|
+
...details,
|
|
71
|
+
...(source.details || {}),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function selectionResult(command, requested, results, options = {}) {
|
|
77
|
+
const diagnostics = options.diagnostics || [];
|
|
78
|
+
const summary = {
|
|
79
|
+
total: results.length,
|
|
80
|
+
succeeded: results.filter((entry) => entry.ok && entry.action !== "skip").length,
|
|
81
|
+
skipped: results.filter((entry) => entry.ok && entry.action === "skip").length,
|
|
82
|
+
failed: results.filter((entry) => !entry.ok).length,
|
|
83
|
+
};
|
|
84
|
+
return commandResult(command, {
|
|
85
|
+
scope: "selection",
|
|
86
|
+
requested,
|
|
87
|
+
results,
|
|
88
|
+
summary,
|
|
89
|
+
}, {
|
|
90
|
+
ok: summary.failed === 0 && !diagnostics.some((entry) => (entry.severity || "error") === "error"),
|
|
91
|
+
diagnostics,
|
|
92
|
+
text: options.text || "",
|
|
93
|
+
renderTextOnError: true,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function jsonEnvelope(result) {
|
|
98
|
+
return {
|
|
99
|
+
schemaVersion: result.schemaVersion,
|
|
100
|
+
ok: result.ok,
|
|
101
|
+
command: result.command,
|
|
102
|
+
data: result.data,
|
|
103
|
+
diagnostics: result.diagnostics,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
RESULT_SCHEMA_VERSION,
|
|
109
|
+
commandName,
|
|
110
|
+
commandResult,
|
|
111
|
+
diagnosticFromError,
|
|
112
|
+
failure,
|
|
113
|
+
fromDiagnostics,
|
|
114
|
+
jsonEnvelope,
|
|
115
|
+
normalizeDiagnostic,
|
|
116
|
+
selectionResult,
|
|
117
|
+
success,
|
|
118
|
+
};
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const { loadConfigProjection, requireWorkspaceRoot } = require("./core/config");
|
|
2
|
+
const { normalizeTools } = require("./core/tools");
|
|
3
|
+
const { WorkspaceError } = require("./core/errors");
|
|
4
|
+
const { parse } = require("./cli/parser");
|
|
5
|
+
const { failure } = require("./cli/result");
|
|
6
|
+
const { executeCompletion } = require("./cli/commands/completion");
|
|
7
|
+
const { executeHelp, executeVersion } = require("./cli/commands/help");
|
|
8
|
+
const { executeInit } = require("./cli/commands/init");
|
|
9
|
+
const { executeExtension } = require("./cli/commands/extension");
|
|
10
|
+
const { executeMonitor, readStdinJson } = require("./cli/commands/monitor");
|
|
11
|
+
const { executePermissions } = require("./cli/commands/permissions");
|
|
12
|
+
const { executeProject } = require("./cli/commands/project");
|
|
13
|
+
const { executeProjectBranch } = require("./cli/commands/project-branch");
|
|
14
|
+
const { executeProjectBranchUpdateLatest } = require("./cli/commands/project-branch-update");
|
|
15
|
+
const { executeUpdate, updateWorkspace } = require("./cli/commands/update");
|
|
16
|
+
const {
|
|
17
|
+
executeDoctor,
|
|
18
|
+
executeLanguage,
|
|
19
|
+
} = require("./cli/commands/workspace");
|
|
20
|
+
|
|
21
|
+
async function dispatch(invocation) {
|
|
22
|
+
const key = invocation.definition.path.join(" ");
|
|
23
|
+
if (key === "init") return executeInit(invocation);
|
|
24
|
+
if (key.startsWith("extension ")) return executeExtension(invocation);
|
|
25
|
+
if (key === "monitor" || key === "monitor report") return executeMonitor(invocation);
|
|
26
|
+
if (key === "project branch update-latest") return executeProjectBranchUpdateLatest(invocation);
|
|
27
|
+
if (key.startsWith("project branch ")) return executeProjectBranch(invocation);
|
|
28
|
+
if (key.startsWith("project ")) return executeProject(invocation);
|
|
29
|
+
if (key === "update") return executeUpdate(invocation);
|
|
30
|
+
if (key === "language") return executeLanguage(invocation);
|
|
31
|
+
if (key === "permissions apply") return executePermissions(invocation);
|
|
32
|
+
if (key === "doctor") return executeDoctor(invocation);
|
|
33
|
+
if (key === "completion") return executeCompletion(invocation);
|
|
34
|
+
if (key === "help") return executeHelp();
|
|
35
|
+
if (key === "version") return executeVersion();
|
|
36
|
+
throw Object.assign(new Error(`No handler is registered for command: ${key}`), { code: "CLI_HANDLER_MISSING" });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main(argv) {
|
|
40
|
+
let command = null;
|
|
41
|
+
try {
|
|
42
|
+
const parsed = parse(argv);
|
|
43
|
+
command = parsed.command?.path || null;
|
|
44
|
+
if (parsed.options.version || parsed.command?.path[0] === "version") return executeVersion();
|
|
45
|
+
if (parsed.options.help || !parsed.command || parsed.command.path[0] === "help") return executeHelp();
|
|
46
|
+
const root = parsed.command.workspace === "required" ? requireWorkspaceRoot(process.cwd()) : process.cwd();
|
|
47
|
+
const config = parsed.command.config.length > 0 && parsed.command.path[0] !== "doctor"
|
|
48
|
+
? loadConfigProjection(root, parsed.command.config)
|
|
49
|
+
: null;
|
|
50
|
+
return await dispatch({
|
|
51
|
+
definition: parsed.command,
|
|
52
|
+
args: parsed.args,
|
|
53
|
+
options: parsed.options,
|
|
54
|
+
root,
|
|
55
|
+
config,
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error.code !== "CLI_UNKNOWN_COMMAND") command ||= error.details?.command || null;
|
|
59
|
+
if (!error.code && command) {
|
|
60
|
+
const commandText = Array.isArray(command) ? command.join("_") : String(command).replace(/\./g, "_");
|
|
61
|
+
const code = `${commandText.replace(/[^a-z0-9_]/gi, "_").toUpperCase()}_FAILED`;
|
|
62
|
+
error = new WorkspaceError(code, error.message, { cause: error.name });
|
|
63
|
+
}
|
|
64
|
+
return failure(error, command);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toolsOption(value) {
|
|
69
|
+
return value === undefined ? ["claude", "codex"] : normalizeTools(value, "cli");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function monitorCommand(action, options = {}) {
|
|
73
|
+
return executeMonitor({ definition: { path: action ? ["monitor", action] : ["monitor"] }, options });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { dispatch, main, monitorCommand, parse, readStdinJson, toolsOption, updateWorkspace };
|