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,193 @@
|
|
|
1
|
+
const { loadState } = require("../../core/config");
|
|
2
|
+
const { WorkspaceError } = require("../../core/errors");
|
|
3
|
+
const {
|
|
4
|
+
applyExtensionUninstall,
|
|
5
|
+
discoverExtensions,
|
|
6
|
+
inspectExtensionState,
|
|
7
|
+
normalizeExtensionNames,
|
|
8
|
+
planExtensionUninstall,
|
|
9
|
+
prepareExtensionPlans,
|
|
10
|
+
runExtensionBatch,
|
|
11
|
+
} = require("../../core/extensions");
|
|
12
|
+
const { loadInitManifest } = require("../../core/init");
|
|
13
|
+
const { acquireInitLock } = require("../../core/init-lock");
|
|
14
|
+
const { resolveWorkspaceTools } = require("../../core/tools");
|
|
15
|
+
const { createInteractiveUi } = require("../../init/ui");
|
|
16
|
+
const { confirm } = require("../confirmation");
|
|
17
|
+
const { selectionResult, success } = require("../result");
|
|
18
|
+
|
|
19
|
+
function formatUninstallPlan(plan) {
|
|
20
|
+
if (plan.action === "skip") return `Extension ${plan.id} is not installed.`;
|
|
21
|
+
return [
|
|
22
|
+
`Uninstall extension ${plan.id}@${plan.version}:`,
|
|
23
|
+
...plan.targets.map((target) => ` REMOVE ${target}`),
|
|
24
|
+
" REMOVE .code-workspace/ext-manifest.json entry",
|
|
25
|
+
].join("\n");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatInstallPlan(plans) {
|
|
29
|
+
return [
|
|
30
|
+
`Install ${plans.length} extension${plans.length === 1 ? "" : "s"}:`,
|
|
31
|
+
...plans.flatMap((plan) => [
|
|
32
|
+
` ${plan.id}@${plan.version} [Extension Spec ${plan.extensionSpecVersion}] (manifest ${plan.manifestSha256})`,
|
|
33
|
+
...(plan.capabilities.networkHosts || []).map((host) => ` NETWORK https://${host}`),
|
|
34
|
+
...plan.artifacts.map((artifact) => ` WRITE ${artifact.target} (${artifact.kind})`),
|
|
35
|
+
]),
|
|
36
|
+
].join("\n");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function collectExtensionInstallSelection(catalog, state, options = {}) {
|
|
40
|
+
const ui = options.ui || await createInteractiveUi({
|
|
41
|
+
...options,
|
|
42
|
+
cancelCode: "EXTENSION_INSTALL_CANCELLED",
|
|
43
|
+
cancelMessage: "Extension installation cancelled. No changes were made.",
|
|
44
|
+
});
|
|
45
|
+
const installed = new Set(Object.entries(state.extensions || {}).filter(([, value]) => value.installed).map(([id]) => id));
|
|
46
|
+
const choices = catalog.map((entry) => ({
|
|
47
|
+
value: entry.id,
|
|
48
|
+
label: `${entry.id} · ${entry.name} · ${entry.latestSupported ? `latest supported: ${entry.latestSupported.version} · Extension Spec ${entry.latestSupported.extensionSpecVersion}` : "no supported Extension Spec"}${installed.has(entry.id) ? " · installed" : ""}`,
|
|
49
|
+
...(entry.latestSupported ? {} : { disabled: true }),
|
|
50
|
+
}));
|
|
51
|
+
ui.intro("Code Workspace extensions");
|
|
52
|
+
if (choices.length === 0) {
|
|
53
|
+
ui.close("No built-in extensions are available.");
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const selected = await ui.multiselect("Extensions (select any)", choices, []);
|
|
57
|
+
ui.close(selected.length > 0 ? "Extension selection ready." : "No extensions selected.");
|
|
58
|
+
return normalizeExtensionNames(selected);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function installResultEntry(entry) {
|
|
62
|
+
const failed = entry.status === "failed";
|
|
63
|
+
return {
|
|
64
|
+
name: entry.id,
|
|
65
|
+
ok: !failed,
|
|
66
|
+
action: entry.status === "skipped" ? "skip" : "install",
|
|
67
|
+
status: entry.status,
|
|
68
|
+
version: entry.version,
|
|
69
|
+
extensionSpecVersion: entry.extensionSpecVersion,
|
|
70
|
+
...(entry.reason ? { reason: entry.reason } : {}),
|
|
71
|
+
...(entry.artifacts ? { artifacts: entry.artifacts } : {}),
|
|
72
|
+
...(entry.code ? { code: entry.code } : {}),
|
|
73
|
+
...(entry.message ? { message: entry.message } : {}),
|
|
74
|
+
...(entry.statePersisted !== undefined ? { statePersisted: entry.statePersisted } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function installResultText(results) {
|
|
79
|
+
const lines = results.map((entry) => entry.status === "installed"
|
|
80
|
+
? `Installed ${entry.id}@${entry.version}.`
|
|
81
|
+
: entry.status === "skipped"
|
|
82
|
+
? `Skipped ${entry.id}@${entry.version}: already current.`
|
|
83
|
+
: `Failed ${entry.id}${entry.version ? `@${entry.version}` : ""}: ${entry.message}`);
|
|
84
|
+
const installed = results.filter((entry) => entry.status === "installed").length;
|
|
85
|
+
const skipped = results.filter((entry) => entry.status === "skipped").length;
|
|
86
|
+
const failed = results.filter((entry) => entry.status === "failed").length;
|
|
87
|
+
lines.push(`Extensions: ${installed} installed, ${skipped} skipped, ${failed} failed.`);
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function executeExtensionInstall(invocation) {
|
|
92
|
+
const command = "extension.install";
|
|
93
|
+
const dependencies = invocation.dependencies || {};
|
|
94
|
+
const interactive = dependencies.interactive ?? (!invocation.options.json && invocation.options.yes !== true && process.stdin.isTTY && process.stdout.isTTY);
|
|
95
|
+
let requested = invocation.args.length > 0 ? normalizeExtensionNames(invocation.args) : null;
|
|
96
|
+
if (requested === null && !interactive) {
|
|
97
|
+
throw new WorkspaceError("EXTENSION_SELECTION_REQUIRED", "Extension installation requires one or more extension names outside interactive mode.", {
|
|
98
|
+
remediation: "Pass one or more names, for example: code-w extension install openspec-workspace --yes",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const catalogResult = discoverExtensions({ tolerant: true, ...(dependencies.extensionsRoot ? { extensionsRoot: dependencies.extensionsRoot } : {}) });
|
|
103
|
+
const stateInspection = inspectExtensionState(invocation.root);
|
|
104
|
+
if (requested === null) {
|
|
105
|
+
try {
|
|
106
|
+
requested = await (dependencies.collectExtensionInstallSelection || collectExtensionInstallSelection)(catalogResult.catalog, stateInspection.state, dependencies);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.code === "EXTENSION_INSTALL_CANCELLED") {
|
|
109
|
+
return success(command, { action: "cancel", scope: "selection", requested: [], results: [], summary: { total: 0, succeeded: 0, skipped: 0, failed: 0 } }, "Extension installation cancelled. No changes were made.");
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (requested.length === 0) {
|
|
115
|
+
return success(command, { action: "skip", scope: "selection", requested: [], results: [], summary: { total: 0, succeeded: 0, skipped: 0, failed: 0 } }, "No extensions selected. No changes were made.");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const tools = resolveWorkspaceTools({ state: loadState(invocation.root), manifestTools: loadInitManifest().tools }).tools;
|
|
119
|
+
const preparation = prepareExtensionPlans(catalogResult, requested, {
|
|
120
|
+
tools,
|
|
121
|
+
state: stateInspection.state,
|
|
122
|
+
stateError: stateInspection.error,
|
|
123
|
+
});
|
|
124
|
+
if (preparation.plans.length > 0) {
|
|
125
|
+
const planText = formatInstallPlan(preparation.plans);
|
|
126
|
+
if (!(await (dependencies.confirm || confirm)(`${planText}\nContinue?`, invocation.options))) {
|
|
127
|
+
throw new WorkspaceError("CLI_CANCELLED", "Extension installation cancelled.");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const workspace = invocation.config.workspace;
|
|
131
|
+
const batch = runExtensionBatch(invocation.root, preparation.plans, (extension) => ({
|
|
132
|
+
schemaVersion: 1,
|
|
133
|
+
extensionSpecVersion: extension.extensionSpecVersion,
|
|
134
|
+
extension: { id: extension.id, version: extension.version },
|
|
135
|
+
workspace: { name: workspace.name, uuid: workspace.uuid, language: workspace.language },
|
|
136
|
+
tools,
|
|
137
|
+
}), { requested, preFailures: preparation.failures });
|
|
138
|
+
const failedIds = new Set(batch.results.filter((entry) => entry.status === "failed").map((entry) => entry.id));
|
|
139
|
+
const diagnostics = [
|
|
140
|
+
...preparation.diagnostics.filter((entry) => !entry.extension || !failedIds.has(entry.extension)),
|
|
141
|
+
...batch.results.filter((entry) => entry.status === "failed").map((entry) => ({
|
|
142
|
+
code: entry.code || "EXTENSION_INSTALL_FAILED",
|
|
143
|
+
severity: "error",
|
|
144
|
+
message: entry.message || `Extension ${entry.id} failed to install.`,
|
|
145
|
+
extension: entry.id,
|
|
146
|
+
version: entry.version,
|
|
147
|
+
})),
|
|
148
|
+
...batch.results.flatMap((entry) => (entry.warnings || []).map((warning) => ({
|
|
149
|
+
code: warning.code,
|
|
150
|
+
severity: "warning",
|
|
151
|
+
message: warning.message,
|
|
152
|
+
extension: entry.id,
|
|
153
|
+
version: entry.version,
|
|
154
|
+
}))),
|
|
155
|
+
];
|
|
156
|
+
return selectionResult(command, requested, batch.results.map(installResultEntry), {
|
|
157
|
+
diagnostics,
|
|
158
|
+
text: installResultText(batch.results),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function executeExtensionUninstall(invocation) {
|
|
163
|
+
const command = "extension.uninstall";
|
|
164
|
+
const plan = planExtensionUninstall(invocation.root, invocation.args[0]);
|
|
165
|
+
const planText = formatUninstallPlan(plan);
|
|
166
|
+
if (plan.action === "remove" && !(await confirm(`${planText}\nContinue?`, invocation.options))) {
|
|
167
|
+
throw new WorkspaceError("CLI_CANCELLED", "Extension uninstall cancelled.");
|
|
168
|
+
}
|
|
169
|
+
const result = applyExtensionUninstall(plan, invocation.dependencies);
|
|
170
|
+
const text = result.status === "skipped" ? planText : `${planText}\nExtension uninstalled and verified.`;
|
|
171
|
+
return success(command, { ...result, targets: plan.targets }, text);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function executeExtension(invocation) {
|
|
175
|
+
const command = invocation.definition.path.join(".");
|
|
176
|
+
const releaseLock = await (invocation.dependencies?.acquireInitLock || acquireInitLock)(invocation.root);
|
|
177
|
+
try {
|
|
178
|
+
if (command === "extension.install") return await executeExtensionInstall(invocation);
|
|
179
|
+
if (command === "extension.uninstall") return await executeExtensionUninstall(invocation);
|
|
180
|
+
throw new WorkspaceError("CLI_HANDLER_MISSING", `Unsupported extension action: ${command}`);
|
|
181
|
+
} finally {
|
|
182
|
+
await releaseLock();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
collectExtensionInstallSelection,
|
|
188
|
+
executeExtension,
|
|
189
|
+
executeExtensionInstall,
|
|
190
|
+
executeExtensionUninstall,
|
|
191
|
+
formatInstallPlan,
|
|
192
|
+
formatUninstallPlan,
|
|
193
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const packageJson = require("../../../package.json");
|
|
2
|
+
const { SUPPORTED_LANGUAGES } = require("../../core/language");
|
|
3
|
+
const { commandHelpRows, GLOBAL_OPTIONS } = require("../registry");
|
|
4
|
+
const { success } = require("../result");
|
|
5
|
+
|
|
6
|
+
function optionUsage(name, definition) {
|
|
7
|
+
const aliases = (definition.aliases || []).map((alias) => `-${alias}`).join(", ");
|
|
8
|
+
const long = `--${name}${definition.type === "string" ? ` <${name}>` : ""}`;
|
|
9
|
+
return aliases ? `${aliases}, ${long}` : long;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function helpText() {
|
|
13
|
+
const rows = commandHelpRows();
|
|
14
|
+
const commands = rows.map(({ usage, summary }) => ` ${usage.padEnd(36)}${summary}`).join("\n");
|
|
15
|
+
const commandOptions = rows
|
|
16
|
+
.filter(({ command }) => Object.keys(command.options).length > 0)
|
|
17
|
+
.map(({ command }) => {
|
|
18
|
+
const label = command.path.join(" ");
|
|
19
|
+
const options = Object.entries(command.options).map(([name, definition]) => ` ${optionUsage(name, definition)}`).join("\n");
|
|
20
|
+
return ` ${label}:\n${options}`;
|
|
21
|
+
})
|
|
22
|
+
.join("\n");
|
|
23
|
+
const globalOptions = Object.entries(GLOBAL_OPTIONS).map(([name, definition]) => optionUsage(name, definition)).join(", ");
|
|
24
|
+
const languages = SUPPORTED_LANGUAGES.map((entry) => entry.value).join("|");
|
|
25
|
+
return `Code Workspace ${packageJson.version}\n\nUsage: code-workspace [command] [options]\n\nCommands:\n${commands}\n\nCommand options:\n${commandOptions}\n\nWorkspace languages: ${languages}\nGlobal options: ${globalOptions}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function executeHelp() {
|
|
29
|
+
return success("help", { version: packageJson.version, commands: commandHelpRows().map(({ usage, summary }) => ({ usage, summary })) }, helpText());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function executeVersion() {
|
|
33
|
+
return success("version", { version: packageJson.version }, packageJson.version);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { executeHelp, executeVersion, helpText, optionUsage };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
|
|
3
|
+
const { loadState } = require("../../core/config");
|
|
4
|
+
const { WorkspaceError } = require("../../core/errors");
|
|
5
|
+
const { acquireInitLock } = require("../../core/init-lock");
|
|
6
|
+
const {
|
|
7
|
+
discoverExtensions,
|
|
8
|
+
emptyExtensionState,
|
|
9
|
+
hasWorkspaceConfiguration,
|
|
10
|
+
inspectExtensionState,
|
|
11
|
+
installedExtensionNames,
|
|
12
|
+
parseExtensionSelection,
|
|
13
|
+
prepareExtensionPlans,
|
|
14
|
+
runExtensionBatch,
|
|
15
|
+
} = require("../../core/extensions");
|
|
16
|
+
const { compareVersions, loadInitManifest, minimumFromRange, runCommand } = require("../../core/init");
|
|
17
|
+
const { initializeWorkspace } = require("../../core/initializer");
|
|
18
|
+
const { resolveWorkspaceTools } = require("../../core/tools");
|
|
19
|
+
const { collectInitPlan } = require("../../init/wizard");
|
|
20
|
+
const { success } = require("../result");
|
|
21
|
+
|
|
22
|
+
function migrationData(plan) {
|
|
23
|
+
if (!plan) return null;
|
|
24
|
+
return {
|
|
25
|
+
fromVersion: plan.schema.fromVersion,
|
|
26
|
+
toVersion: plan.schema.toVersion,
|
|
27
|
+
changed: plan.changed,
|
|
28
|
+
steps: plan.steps,
|
|
29
|
+
schemaSteps: plan.schema.steps,
|
|
30
|
+
language: plan.language,
|
|
31
|
+
writeTargets: plan.writeTargets,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function executeInit(invocation) {
|
|
36
|
+
const root = path.resolve(invocation.args[0] || ".");
|
|
37
|
+
const releaseInitLock = await acquireInitLock(root);
|
|
38
|
+
try {
|
|
39
|
+
return await executeInitUnlocked(invocation, root);
|
|
40
|
+
} finally {
|
|
41
|
+
await releaseInitLock();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function executeInitUnlocked(invocation, root) {
|
|
46
|
+
const { options } = invocation;
|
|
47
|
+
if (options.json && options.yes !== true) {
|
|
48
|
+
throw new WorkspaceError("CLI_CONFIRMATION_REQUIRED", "Workspace initialization requires explicit confirmation in JSON mode.", {
|
|
49
|
+
remediation: "Re-run with --yes.",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const run = options.json
|
|
53
|
+
? (command, commandArgs, runOptions = {}) => runCommand(command, commandArgs, { ...runOptions, capture: true })
|
|
54
|
+
: runCommand;
|
|
55
|
+
const manifest = loadInitManifest();
|
|
56
|
+
const minimumNode = minimumFromRange(manifest.requirements.node);
|
|
57
|
+
if (compareVersions(process.versions.node, minimumNode) < 0) {
|
|
58
|
+
throw new WorkspaceError("NODE_VERSION_UNSUPPORTED", `Node ${minimumNode} or newer is required; found ${process.versions.node}`, {
|
|
59
|
+
actual: process.versions.node,
|
|
60
|
+
required: minimumNode,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const existingState = loadState(root);
|
|
64
|
+
const explicitExtensions = parseExtensionSelection(options.extensions);
|
|
65
|
+
const resolvedTools = resolveWorkspaceTools({
|
|
66
|
+
explicit: options.tools,
|
|
67
|
+
state: existingState,
|
|
68
|
+
manifestTools: manifest.tools,
|
|
69
|
+
});
|
|
70
|
+
const interactive = !options.json && !options.yes && process.stdin.isTTY && process.stdout.isTTY;
|
|
71
|
+
const existingWorkspace = hasWorkspaceConfiguration(root);
|
|
72
|
+
const inspectExtensions = interactive || explicitExtensions?.length > 0 || (explicitExtensions === null && existingWorkspace);
|
|
73
|
+
const extensionStateInspection = inspectExtensions ? inspectExtensionState(root) : { state: emptyExtensionState(), error: null };
|
|
74
|
+
const extensionState = extensionStateInspection.state;
|
|
75
|
+
const defaultExtensions = existingWorkspace && !extensionStateInspection.error ? installedExtensionNames(extensionState) : [];
|
|
76
|
+
const extensionCatalogResult = inspectExtensions ? discoverExtensions({ tolerant: true }) : { catalog: [], invalid: [] };
|
|
77
|
+
const extensionCatalog = extensionStateInspection.error && interactive ? [] : extensionCatalogResult.catalog;
|
|
78
|
+
if (interactive && explicitExtensions !== null) {
|
|
79
|
+
prepareExtensionPlans(extensionCatalogResult, explicitExtensions, { tools: resolvedTools.tools, state: extensionState, stateError: extensionStateInspection.error });
|
|
80
|
+
}
|
|
81
|
+
const interactiveExplicitExtensions = explicitExtensions === null
|
|
82
|
+
? undefined
|
|
83
|
+
: explicitExtensions.filter((id) => extensionCatalog.some((entry) => entry.id === id && entry.latestSupported));
|
|
84
|
+
let plan = null;
|
|
85
|
+
if (interactive) {
|
|
86
|
+
try {
|
|
87
|
+
plan = await collectInitPlan(root, manifest, {
|
|
88
|
+
run,
|
|
89
|
+
tools: options.tools !== undefined ? resolvedTools.tools : undefined,
|
|
90
|
+
initialTools: resolvedTools.tools,
|
|
91
|
+
workspaceName: options["workspace-name"],
|
|
92
|
+
monitor: options.monitor === true ? true : options["no-monitor"] === true ? false : undefined,
|
|
93
|
+
monitorUrl: options["monitor-url"],
|
|
94
|
+
language: options.language,
|
|
95
|
+
extensionCatalog,
|
|
96
|
+
extensionState,
|
|
97
|
+
extensions: interactiveExplicitExtensions,
|
|
98
|
+
initialExtensions: explicitExtensions === null ? defaultExtensions : interactiveExplicitExtensions,
|
|
99
|
+
});
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error.code === "INIT_CANCELLED") {
|
|
102
|
+
return success("init", { action: "cancel" }, "Initialization cancelled. No changes were made.");
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const tools = plan?.tools || resolvedTools.tools;
|
|
108
|
+
const requestedExtensions = explicitExtensions !== null
|
|
109
|
+
? explicitExtensions
|
|
110
|
+
: plan ? plan.extensions.map((entry) => entry.id) : defaultExtensions;
|
|
111
|
+
const extensionPreparation = prepareExtensionPlans(extensionCatalogResult, requestedExtensions, { tools, state: extensionState, stateError: extensionStateInspection.error });
|
|
112
|
+
const extensionPlans = extensionPreparation.plans;
|
|
113
|
+
const toolSelection = { tools, source: plan ? (options.tools !== undefined ? "cli" : "interactive") : resolvedTools.source };
|
|
114
|
+
const result = await initializeWorkspace(root, {
|
|
115
|
+
run,
|
|
116
|
+
tools,
|
|
117
|
+
force: options.force === true,
|
|
118
|
+
yes: plan ? true : options.yes === true,
|
|
119
|
+
workspaceName: plan?.workspace.name || options["workspace-name"],
|
|
120
|
+
workspaceUuid: plan?.workspace.uuid,
|
|
121
|
+
monitor: plan?.monitor.enable ?? (options.monitor === true ? true : options["no-monitor"] === true ? false : undefined),
|
|
122
|
+
monitorUrl: plan?.monitor.url || options["monitor-url"],
|
|
123
|
+
language: plan?.language || options.language,
|
|
124
|
+
interactive: false,
|
|
125
|
+
initPlan: plan,
|
|
126
|
+
onStage: null,
|
|
127
|
+
});
|
|
128
|
+
const extensionResult = runExtensionBatch(root, extensionPlans, (extension) => ({
|
|
129
|
+
schemaVersion: 1,
|
|
130
|
+
extensionSpecVersion: extension.extensionSpecVersion,
|
|
131
|
+
extension: { id: extension.id, version: extension.version },
|
|
132
|
+
workspace: {
|
|
133
|
+
name: result.workspaceConfig.workspace.name,
|
|
134
|
+
uuid: result.workspaceConfig.workspace.uuid,
|
|
135
|
+
language: result.language,
|
|
136
|
+
},
|
|
137
|
+
tools,
|
|
138
|
+
}), { requested: requestedExtensions, preFailures: extensionPreparation.failures });
|
|
139
|
+
const extensionDiagnostics = [
|
|
140
|
+
...extensionPreparation.diagnostics,
|
|
141
|
+
...extensionResult.results
|
|
142
|
+
.filter((entry) => entry.status === "failed")
|
|
143
|
+
.map((entry) => ({
|
|
144
|
+
code: "EXTENSION_INIT_FAILED",
|
|
145
|
+
severity: "warning",
|
|
146
|
+
message: `Extension ${entry.id}@${entry.version} failed: ${entry.message}`,
|
|
147
|
+
extension: entry.id,
|
|
148
|
+
version: entry.version,
|
|
149
|
+
causeCode: entry.code,
|
|
150
|
+
})),
|
|
151
|
+
];
|
|
152
|
+
for (const entry of extensionResult.results) {
|
|
153
|
+
for (const warning of entry.warnings || []) {
|
|
154
|
+
extensionDiagnostics.push({
|
|
155
|
+
code: warning.code,
|
|
156
|
+
severity: "warning",
|
|
157
|
+
message: warning.message,
|
|
158
|
+
extension: entry.id,
|
|
159
|
+
version: entry.version,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const data = {
|
|
164
|
+
root: result.root,
|
|
165
|
+
releaseVersion: result.manifest.releaseVersion,
|
|
166
|
+
nodeVersion: result.nodeVersion,
|
|
167
|
+
dependencies: result.dependencies,
|
|
168
|
+
obsoleteFiles: result.obsoleteFiles,
|
|
169
|
+
managedFiles: result.managedFiles,
|
|
170
|
+
localConfig: result.localConfig,
|
|
171
|
+
workspace: result.workspaceConfig.workspace,
|
|
172
|
+
monitor: result.workspaceConfig.monitor,
|
|
173
|
+
language: result.language,
|
|
174
|
+
tools: toolSelection,
|
|
175
|
+
migration: migrationData(result.migration),
|
|
176
|
+
permissions: result.permissions,
|
|
177
|
+
verification: result.verification,
|
|
178
|
+
plan: result.initPlan,
|
|
179
|
+
stages: result.stages.map((stage) => stage.name),
|
|
180
|
+
extensions: extensionResult,
|
|
181
|
+
};
|
|
182
|
+
const lines = [
|
|
183
|
+
"Code Workspace is ready.",
|
|
184
|
+
`Workspace: ${result.workspaceConfig.workspace.name} (${result.workspaceConfig.workspace.uuid})`,
|
|
185
|
+
`Language: ${result.language}`,
|
|
186
|
+
`Tools: ${tools.length ? tools.join(", ") : "none"} (${toolSelection.source})`,
|
|
187
|
+
`Extensions: ${extensionResult.summary.installed} installed, ${extensionResult.summary.skipped} skipped, ${extensionResult.summary.failed} failed`,
|
|
188
|
+
];
|
|
189
|
+
if (result.workspaceConfig.monitor.enable) {
|
|
190
|
+
lines.push(`Codex monitoring reports to ${result.workspaceConfig.monitor.url}. Review and trust the project hooks with \`/hooks\` in Codex.`);
|
|
191
|
+
}
|
|
192
|
+
if (result.localConfig.action === "write" || result.permissions.action === "skip") {
|
|
193
|
+
lines.push(tools.length > 0
|
|
194
|
+
? "Add local projects with the `code-workspace-add-projects` skill."
|
|
195
|
+
: "Add local projects with `code-workspace project inspect`, then register a complete project record.");
|
|
196
|
+
}
|
|
197
|
+
return success("init", data, lines.join("\n"), extensionDiagnostics);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = { executeInit, migrationData };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { loadConfigProjection, requireWorkspaceRoot } = require("../../core/config");
|
|
2
|
+
const { WorkspaceError } = require("../../core/errors");
|
|
3
|
+
const { reportHookEvent, startMonitor, validatePort } = require("../../monitor");
|
|
4
|
+
const { success } = require("../result");
|
|
5
|
+
|
|
6
|
+
async function readStdinJson(input = process.stdin) {
|
|
7
|
+
const chunks = [];
|
|
8
|
+
for await (const chunk of input) chunks.push(chunk);
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
throw new WorkspaceError("MONITOR_EVENT_INVALID", `Cannot parse monitor event JSON: ${error.message}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function executeMonitor(invocation) {
|
|
17
|
+
const action = invocation.definition.path[1];
|
|
18
|
+
if (action === "report") {
|
|
19
|
+
try {
|
|
20
|
+
const root = requireWorkspaceRoot(process.cwd());
|
|
21
|
+
const report = await reportHookEvent(await readStdinJson(), loadConfigProjection(root, ["identity", "monitor"]));
|
|
22
|
+
return success("monitor.report", report);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return success("monitor.report", { action: "skip", reason: "reporting failed open", errorCode: error.code || "MONITOR_REPORT_FAILED" });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (invocation.options.json) {
|
|
28
|
+
throw new WorkspaceError("CLI_JSON_UNSUPPORTED", "The long-running monitor command does not support --json; use the dashboard API for machine-readable state.");
|
|
29
|
+
}
|
|
30
|
+
const port = validatePort(invocation.options.port);
|
|
31
|
+
let monitor;
|
|
32
|
+
try {
|
|
33
|
+
monitor = await startMonitor({ port });
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error.code === "EADDRINUSE") {
|
|
36
|
+
throw new WorkspaceError("MONITOR_PORT_IN_USE", `Monitor port 127.0.0.1:${port} is already in use.`, {
|
|
37
|
+
port,
|
|
38
|
+
remediation: `Start another port with code-w monitor -p 8080, then update monitor.url.`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
const text = [
|
|
44
|
+
`Code Workspace Monitor listening on http://${monitor.host}:${monitor.port}`,
|
|
45
|
+
`Dashboard: http://${monitor.host}:${monitor.port}/`,
|
|
46
|
+
`Workspaces report to http://${monitor.host}:${monitor.port}/api/v1/events`,
|
|
47
|
+
].join("\n");
|
|
48
|
+
process.stdout.write(`${text}\n`);
|
|
49
|
+
await new Promise((resolve) => {
|
|
50
|
+
let closing = false;
|
|
51
|
+
const close = () => {
|
|
52
|
+
if (closing) return;
|
|
53
|
+
closing = true;
|
|
54
|
+
monitor.server.close(resolve);
|
|
55
|
+
};
|
|
56
|
+
process.once("SIGINT", close);
|
|
57
|
+
process.once("SIGTERM", close);
|
|
58
|
+
});
|
|
59
|
+
return success("monitor", { host: monitor.host, port: monitor.port });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { executeMonitor, readStdinJson };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { loadState } = require("../../core/config");
|
|
2
|
+
const { loadInitManifest } = require("../../core/init");
|
|
3
|
+
const { applyPermissionPlan, formatPermissionPlan, planPermissionChanges } = require("../../core/permissions");
|
|
4
|
+
const { resolveWorkspaceTools } = require("../../core/tools");
|
|
5
|
+
const { validateProjects } = require("../../core/validation");
|
|
6
|
+
const { confirm } = require("../confirmation");
|
|
7
|
+
const { fromDiagnostics, success } = require("../result");
|
|
8
|
+
|
|
9
|
+
async function executePermissions(invocation) {
|
|
10
|
+
const command = "permissions.apply";
|
|
11
|
+
const validation = validateProjects(invocation.root, invocation.config);
|
|
12
|
+
if (validation.errors.length > 0) return fromDiagnostics(command, validation);
|
|
13
|
+
const toolSelection = resolveWorkspaceTools({
|
|
14
|
+
explicit: invocation.options.tools,
|
|
15
|
+
state: loadState(invocation.root),
|
|
16
|
+
manifestTools: loadInitManifest().tools,
|
|
17
|
+
});
|
|
18
|
+
const plan = planPermissionChanges({
|
|
19
|
+
root: invocation.root,
|
|
20
|
+
tools: toolSelection.tools,
|
|
21
|
+
grants: invocation.config.projects.map((project) => project.location),
|
|
22
|
+
});
|
|
23
|
+
const planText = formatPermissionPlan(plan);
|
|
24
|
+
if (plan.action === "write" && !(await confirm(`${planText}\nApply these Agent authorization changes?`, invocation.options))) {
|
|
25
|
+
const error = new Error("Agent authorization changes cancelled.");
|
|
26
|
+
error.code = "CLI_CANCELLED";
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
const result = applyPermissionPlan(plan, invocation.options.dependencies);
|
|
30
|
+
return success(command, { ...result, toolSelection }, plan.action === "skip" ? planText : `${planText}\nAgent authorization changes applied and verified.`, validation.diagnostics);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { executePermissions };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const { WorkspaceError } = require("../../core/errors");
|
|
2
|
+
const {
|
|
3
|
+
applyLatestBranchUpdate,
|
|
4
|
+
planLatestBranchUpdate,
|
|
5
|
+
} = require("../../core/project-branch-update");
|
|
6
|
+
const {
|
|
7
|
+
diagnosticFromError,
|
|
8
|
+
selectionResult,
|
|
9
|
+
success,
|
|
10
|
+
} = require("../result");
|
|
11
|
+
|
|
12
|
+
function updateError(code, message, details = {}) {
|
|
13
|
+
return new WorkspaceError(code, message, details);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function uniqueProjectSelection(args) {
|
|
17
|
+
const requested = [...args];
|
|
18
|
+
const names = [];
|
|
19
|
+
const diagnostics = [];
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
for (const name of requested) {
|
|
22
|
+
if (!seen.has(name)) {
|
|
23
|
+
seen.add(name);
|
|
24
|
+
names.push(name);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
diagnostics.push({
|
|
28
|
+
code: "CLI_DUPLICATE_ARGUMENT",
|
|
29
|
+
severity: "warning",
|
|
30
|
+
message: `Project ${name} was provided more than once; later occurrences were ignored.`,
|
|
31
|
+
project: name,
|
|
32
|
+
argument: "name",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return { requested, names, diagnostics };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function batchEntry(project, ok, action, data, message) {
|
|
39
|
+
return { project, ok, action, data: data || null, message };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function batchText(results) {
|
|
43
|
+
const lines = results.map((entry) => `${entry.ok ? (entry.action === "skip" ? "SKIP" : "OK") : "FAILED"}\t${entry.project}\t${entry.message}`);
|
|
44
|
+
const succeeded = results.filter((entry) => entry.ok && entry.action !== "skip").length;
|
|
45
|
+
const skipped = results.filter((entry) => entry.ok && entry.action === "skip").length;
|
|
46
|
+
const failed = results.filter((entry) => !entry.ok).length;
|
|
47
|
+
return [...lines, `Summary\ttotal=${results.length}\tsucceeded=${succeeded}\tskipped=${skipped}\tfailed=${failed}`].join("\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function recordFailure(slot, error, diagnostics) {
|
|
51
|
+
diagnostics.push(diagnosticFromError(error, { project: slot.name }));
|
|
52
|
+
slot.result = batchEntry(slot.name, false, "failed", null, error.message);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resultMessage(data) {
|
|
56
|
+
if (data.action === "skip" && data.reason === "disabled") return "latest-branch update disabled";
|
|
57
|
+
if (data.action === "skip" && data.reason === "already-latest") return `already latest: ${data.afterHead || data.targetHead}`;
|
|
58
|
+
return `fast-forwarded ${data.project?.name || "project"}: ${data.beforeHead} -> ${data.afterHead}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function executeSingle(invocation, dependencies) {
|
|
62
|
+
const { config, root, args } = invocation;
|
|
63
|
+
const plan = (dependencies.planLatestBranchUpdate || planLatestBranchUpdate)(config, args[0], dependencies);
|
|
64
|
+
const applied = (dependencies.applyLatestBranchUpdate || applyLatestBranchUpdate)(root, plan, dependencies);
|
|
65
|
+
return success("project.branch.update-latest", applied, resultMessage(applied));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function executeBatch(invocation, dependencies) {
|
|
69
|
+
const { config, root } = invocation;
|
|
70
|
+
const selection = uniqueProjectSelection(invocation.args);
|
|
71
|
+
const diagnostics = [...selection.diagnostics];
|
|
72
|
+
const slots = selection.names.map((name) => ({ name }));
|
|
73
|
+
for (const slot of slots) {
|
|
74
|
+
try {
|
|
75
|
+
slot.plan = (dependencies.planLatestBranchUpdate || planLatestBranchUpdate)(config, slot.name, dependencies);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
recordFailure(slot, error, diagnostics);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const slot of slots.filter((entry) => entry.plan && !entry.result)) {
|
|
81
|
+
try {
|
|
82
|
+
const applied = (dependencies.applyLatestBranchUpdate || applyLatestBranchUpdate)(root, slot.plan, dependencies);
|
|
83
|
+
slot.result = batchEntry(slot.name, true, applied.action, applied, resultMessage(applied));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
recordFailure(slot, error, diagnostics);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return selectionResult("project.branch.update-latest", selection.requested, slots.map((slot) => slot.result), {
|
|
89
|
+
diagnostics,
|
|
90
|
+
text: batchText(slots.map((slot) => slot.result)),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function executeProjectBranchUpdateLatest(invocation) {
|
|
95
|
+
const dependencies = invocation.options.dependencies || {};
|
|
96
|
+
if (invocation.args.length > 1) return executeBatch(invocation, dependencies);
|
|
97
|
+
if (invocation.args.length !== 1) throw updateError("CLI_ARGUMENT_REQUIRED", "project branch update-latest requires <name>", { argument: "name" });
|
|
98
|
+
return executeSingle(invocation, dependencies);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
batchText,
|
|
103
|
+
executeProjectBranchUpdateLatest,
|
|
104
|
+
executeBatch,
|
|
105
|
+
executeSingle,
|
|
106
|
+
uniqueProjectSelection,
|
|
107
|
+
};
|