codiedev 0.7.10 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +84 -7
- package/dist/commands/ask.js +12 -0
- package/dist/commands/createTicket.d.ts +27 -0
- package/dist/commands/createTicket.js +198 -0
- package/dist/commands/delete.js +12 -0
- package/dist/commands/doctor.js +6 -31
- package/dist/commands/inbox.js +30 -0
- package/dist/commands/library.d.ts +2 -0
- package/dist/commands/library.js +105 -0
- package/dist/commands/note.js +12 -0
- package/dist/commands/ping.js +12 -0
- package/dist/commands/post.d.ts +2 -0
- package/dist/commands/post.js +96 -0
- package/dist/commands/promote.js +12 -0
- package/dist/commands/pull.js +12 -0
- package/dist/commands/push.js +12 -0
- package/dist/commands/react.d.ts +2 -0
- package/dist/commands/react.js +40 -0
- package/dist/commands/reverseTicket.js +30 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +72 -0
- package/dist/commands/send.d.ts +2 -0
- package/dist/commands/send.js +67 -0
- package/dist/commands/share.d.ts +2 -0
- package/dist/commands/share.js +87 -0
- package/dist/commands/shared.d.ts +27 -1
- package/dist/commands/shared.js +63 -2
- package/dist/connect.js +49 -38
- package/dist/createTicketSkill.d.ts +12 -0
- package/dist/createTicketSkill.js +146 -0
- package/dist/detection.d.ts +18 -0
- package/dist/detection.js +28 -0
- package/dist/mcp.js +7 -7
- package/dist/utils.d.ts +22 -0
- package/dist/utils.js +128 -32
- package/package.json +1 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `codiedev share <filename> <recipient> [--role read|edit]` — silently
|
|
3
|
+
// grant a teammate access to an artifact (no notification). Use
|
|
4
|
+
// `codiedev send` instead when the user wants to actively loop someone in.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runShare = runShare;
|
|
7
|
+
const shared_1 = require("./shared");
|
|
8
|
+
const VALID_ROLES = new Set(["read", "edit"]);
|
|
9
|
+
function parseArgs(args) {
|
|
10
|
+
let filename;
|
|
11
|
+
let to;
|
|
12
|
+
let role;
|
|
13
|
+
for (let i = 0; i < args.length; i++) {
|
|
14
|
+
const a = args[i];
|
|
15
|
+
if (a === "--role" && i + 1 < args.length) {
|
|
16
|
+
role = args[++i];
|
|
17
|
+
}
|
|
18
|
+
else if (a.startsWith("--role=")) {
|
|
19
|
+
role = a.slice("--role=".length);
|
|
20
|
+
}
|
|
21
|
+
else if (!a.startsWith("--")) {
|
|
22
|
+
if (filename === undefined)
|
|
23
|
+
filename = a;
|
|
24
|
+
else if (to === undefined)
|
|
25
|
+
to = a;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!filename || !to) {
|
|
29
|
+
console.error("Usage: codiedev share <filename> <recipient> [--role read|edit]");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
if (role && !VALID_ROLES.has(role)) {
|
|
33
|
+
console.error(`Invalid --role. Valid: ${[...VALID_ROLES].join(", ")}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
return { filename, to, role: (role ?? "read") };
|
|
37
|
+
}
|
|
38
|
+
async function runShare(args, configOverride) {
|
|
39
|
+
const parsed = parseArgs(args);
|
|
40
|
+
const config = configOverride ?? (0, shared_1.requireConfig)();
|
|
41
|
+
const start = Date.now();
|
|
42
|
+
try {
|
|
43
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/share-with", {
|
|
44
|
+
config,
|
|
45
|
+
body: {
|
|
46
|
+
filename: parsed.filename,
|
|
47
|
+
to: parsed.to,
|
|
48
|
+
role: parsed.role,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
52
|
+
tool: "codiedev_share_with",
|
|
53
|
+
ok: true,
|
|
54
|
+
latencyMs: Date.now() - start,
|
|
55
|
+
});
|
|
56
|
+
const who = res.recipient ? `${res.recipient.name} (${res.recipient.email})` : parsed.to;
|
|
57
|
+
if (res.noop) {
|
|
58
|
+
console.log(`Already shared with ${who}.`);
|
|
59
|
+
}
|
|
60
|
+
else if (res.updated) {
|
|
61
|
+
console.log(`Updated access for ${who} to ${parsed.role}.`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log(`Shared ${parsed.filename} with ${who} (${parsed.role}).`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
69
|
+
tool: "codiedev_share_with",
|
|
70
|
+
ok: false,
|
|
71
|
+
latencyMs: Date.now() - start,
|
|
72
|
+
error: err.message,
|
|
73
|
+
});
|
|
74
|
+
// Ambiguous recipients return 409 with candidates in the body.
|
|
75
|
+
const body = err.body;
|
|
76
|
+
if (body && Array.isArray(body.candidates) && body.candidates.length > 0) {
|
|
77
|
+
console.error(`Recipient "${parsed.to}" is ambiguous. Candidates:`);
|
|
78
|
+
for (const c of body.candidates) {
|
|
79
|
+
console.error(` - ${c.name} (${c.email})`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.error(`Share failed: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { CodiedevConfig } from "../utils";
|
|
2
|
+
/**
|
|
3
|
+
* Layer per-command env overrides on top of the on-disk config. Useful for
|
|
4
|
+
* ad-hoc testing — point a prod-connected CLI at a dev Convex deployment
|
|
5
|
+
* without re-running `codiedev connect`. Both CODIEDEV_URL and CODIEDEV_TOKEN
|
|
6
|
+
* must be set together (half-overrides would mismatch URL and auth realm).
|
|
7
|
+
* Other fields (companyId, repos[]) come from the file unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export declare function applyConfigEnvOverrides(config: CodiedevConfig): CodiedevConfig;
|
|
2
10
|
/**
|
|
3
11
|
* Require a connected CodieDev config. Exits the process with a helpful
|
|
4
12
|
* message if the user hasn't run `codiedev connect` yet.
|
|
@@ -13,12 +21,30 @@ export declare function apiRequest<T = unknown>(method: "GET" | "POST", path: st
|
|
|
13
21
|
body?: Record<string, unknown>;
|
|
14
22
|
query?: Record<string, string | number | undefined>;
|
|
15
23
|
}): Promise<T>;
|
|
24
|
+
/**
|
|
25
|
+
* Fire-and-forget per-invocation telemetry from CLI subcommands. Mirrors
|
|
26
|
+
* the MCP server's emitTelemetry but stamps `source: "cli"` so adoption
|
|
27
|
+
* dashboards can distinguish between the two surfaces. Never blocks the
|
|
28
|
+
* user's command, never throws — telemetry must not change behavior.
|
|
29
|
+
*/
|
|
30
|
+
export declare function recordAgentEvent(config: CodiedevConfig, event: {
|
|
31
|
+
tool: string;
|
|
32
|
+
ok: boolean;
|
|
33
|
+
latencyMs: number;
|
|
34
|
+
error?: string;
|
|
35
|
+
}): void;
|
|
36
|
+
/**
|
|
37
|
+
* Wraps a CLI subcommand body so success and failure both record telemetry
|
|
38
|
+
* and the original error propagates after the event is queued. Use this in
|
|
39
|
+
* each `runXxx` command instead of inline timing + try/catch boilerplate.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withTelemetry<T>(config: CodiedevConfig, tool: string, fn: () => Promise<T>): Promise<T>;
|
|
16
42
|
/**
|
|
17
43
|
* Relative-time formatter for inbox / listing output.
|
|
18
44
|
*/
|
|
19
45
|
export declare function timeAgo(ts: number): string;
|
|
20
46
|
/**
|
|
21
47
|
* Strip surrounding quotes from a CLI argument if the user wrapped it.
|
|
22
|
-
* `codiedev ping
|
|
48
|
+
* `codiedev ping <name> "hey"` on some shells passes the quotes through.
|
|
23
49
|
*/
|
|
24
50
|
export declare function stripQuotes(s: string): string;
|
package/dist/commands/shared.js
CHANGED
|
@@ -33,14 +33,32 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.applyConfigEnvOverrides = applyConfigEnvOverrides;
|
|
36
37
|
exports.requireConfig = requireConfig;
|
|
37
38
|
exports.apiRequest = apiRequest;
|
|
39
|
+
exports.recordAgentEvent = recordAgentEvent;
|
|
40
|
+
exports.withTelemetry = withTelemetry;
|
|
38
41
|
exports.timeAgo = timeAgo;
|
|
39
42
|
exports.stripQuotes = stripQuotes;
|
|
40
43
|
const https = __importStar(require("https"));
|
|
41
44
|
const http = __importStar(require("http"));
|
|
42
45
|
const utils_1 = require("../utils");
|
|
43
46
|
const version_1 = require("../version");
|
|
47
|
+
/**
|
|
48
|
+
* Layer per-command env overrides on top of the on-disk config. Useful for
|
|
49
|
+
* ad-hoc testing — point a prod-connected CLI at a dev Convex deployment
|
|
50
|
+
* without re-running `codiedev connect`. Both CODIEDEV_URL and CODIEDEV_TOKEN
|
|
51
|
+
* must be set together (half-overrides would mismatch URL and auth realm).
|
|
52
|
+
* Other fields (companyId, repos[]) come from the file unchanged.
|
|
53
|
+
*/
|
|
54
|
+
function applyConfigEnvOverrides(config) {
|
|
55
|
+
const url = process.env.CODIEDEV_URL;
|
|
56
|
+
const token = process.env.CODIEDEV_TOKEN;
|
|
57
|
+
if (url && token) {
|
|
58
|
+
return { ...config, backendUrl: url, token };
|
|
59
|
+
}
|
|
60
|
+
return config;
|
|
61
|
+
}
|
|
44
62
|
/**
|
|
45
63
|
* Require a connected CodieDev config. Exits the process with a helpful
|
|
46
64
|
* message if the user hasn't run `codiedev connect` yet.
|
|
@@ -51,7 +69,7 @@ function requireConfig() {
|
|
|
51
69
|
console.error("Not connected. Run `npx codiedev connect` first and enter your API token.");
|
|
52
70
|
process.exit(1);
|
|
53
71
|
}
|
|
54
|
-
return config;
|
|
72
|
+
return applyConfigEnvOverrides(config);
|
|
55
73
|
}
|
|
56
74
|
/**
|
|
57
75
|
* Perform an authenticated HTTP request against the CodieDev backend.
|
|
@@ -116,6 +134,49 @@ function apiRequest(method, path, options) {
|
|
|
116
134
|
req.end();
|
|
117
135
|
});
|
|
118
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Fire-and-forget per-invocation telemetry from CLI subcommands. Mirrors
|
|
139
|
+
* the MCP server's emitTelemetry but stamps `source: "cli"` so adoption
|
|
140
|
+
* dashboards can distinguish between the two surfaces. Never blocks the
|
|
141
|
+
* user's command, never throws — telemetry must not change behavior.
|
|
142
|
+
*/
|
|
143
|
+
function recordAgentEvent(config, event) {
|
|
144
|
+
apiRequest("POST", "/api/telemetry/mcp-event", {
|
|
145
|
+
config,
|
|
146
|
+
body: {
|
|
147
|
+
tool: event.tool,
|
|
148
|
+
ok: event.ok,
|
|
149
|
+
latencyMs: event.latencyMs,
|
|
150
|
+
error: event.error,
|
|
151
|
+
mcpVersion: version_1.CLI_VERSION,
|
|
152
|
+
source: "cli",
|
|
153
|
+
},
|
|
154
|
+
}).catch(() => {
|
|
155
|
+
// Swallow — telemetry must never affect user-facing flow.
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Wraps a CLI subcommand body so success and failure both record telemetry
|
|
160
|
+
* and the original error propagates after the event is queued. Use this in
|
|
161
|
+
* each `runXxx` command instead of inline timing + try/catch boilerplate.
|
|
162
|
+
*/
|
|
163
|
+
async function withTelemetry(config, tool, fn) {
|
|
164
|
+
const start = Date.now();
|
|
165
|
+
try {
|
|
166
|
+
const result = await fn();
|
|
167
|
+
recordAgentEvent(config, { tool, ok: true, latencyMs: Date.now() - start });
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
recordAgentEvent(config, {
|
|
172
|
+
tool,
|
|
173
|
+
ok: false,
|
|
174
|
+
latencyMs: Date.now() - start,
|
|
175
|
+
error: err.message?.slice(0, 240),
|
|
176
|
+
});
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
119
180
|
/**
|
|
120
181
|
* Relative-time formatter for inbox / listing output.
|
|
121
182
|
*/
|
|
@@ -134,7 +195,7 @@ function timeAgo(ts) {
|
|
|
134
195
|
}
|
|
135
196
|
/**
|
|
136
197
|
* Strip surrounding quotes from a CLI argument if the user wrapped it.
|
|
137
|
-
* `codiedev ping
|
|
198
|
+
* `codiedev ping <name> "hey"` on some shells passes the quotes through.
|
|
138
199
|
*/
|
|
139
200
|
function stripQuotes(s) {
|
|
140
201
|
if ((s.startsWith('"') && s.endsWith('"')) ||
|
package/dist/connect.js
CHANGED
|
@@ -111,7 +111,7 @@ function promptHidden(question) {
|
|
|
111
111
|
stdin.on("data", onData);
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
|
-
function postJson(url, body) {
|
|
114
|
+
function postJson(url, body, extraHeaders = {}) {
|
|
115
115
|
return new Promise((resolve, reject) => {
|
|
116
116
|
const parsed = new URL(url);
|
|
117
117
|
const data = JSON.stringify(body);
|
|
@@ -124,6 +124,7 @@ function postJson(url, body) {
|
|
|
124
124
|
"Content-Type": "application/json",
|
|
125
125
|
"Content-Length": Buffer.byteLength(data),
|
|
126
126
|
"X-Codiedev-Cli-Version": version_1.CLI_VERSION,
|
|
127
|
+
...extraHeaders,
|
|
127
128
|
},
|
|
128
129
|
};
|
|
129
130
|
const lib = parsed.protocol === "https:" ? https : http;
|
|
@@ -213,16 +214,44 @@ async function runConnect(args = []) {
|
|
|
213
214
|
console.error(`\nError: Failed to save config — ${err.message}`);
|
|
214
215
|
process.exit(1);
|
|
215
216
|
}
|
|
217
|
+
// Migration: strip MCP server entries written by pre-CLI-only versions
|
|
218
|
+
// of `codiedev connect`. These pointed at `npx codiedev-mcp`, a binary
|
|
219
|
+
// that was removed in 0.3.4 and that 0.7.11+ no longer ships. Without
|
|
220
|
+
// cleanup, customers upgrading from any prior version see persistent
|
|
221
|
+
// "MCP server failed to start" errors in their agent UIs even though
|
|
222
|
+
// the CLI works fine. Each helper is idempotent and a no-op when the
|
|
223
|
+
// relevant config file doesn't exist.
|
|
224
|
+
//
|
|
225
|
+
// Each call is wrapped because the user's upgrade path matters more
|
|
226
|
+
// than the cleanup. A locked / wrong-owner config file (rare but real)
|
|
227
|
+
// shouldn't block the install loop or strand them on an old version.
|
|
228
|
+
for (const [label, fn] of [
|
|
229
|
+
["Claude Code", utils_1.cleanupClaudeCodeMcp],
|
|
230
|
+
["Codex", utils_1.cleanupCodexMcp],
|
|
231
|
+
["Cursor", utils_1.cleanupCursorMcp],
|
|
232
|
+
["VS Code", utils_1.cleanupVSCodeMcp],
|
|
233
|
+
]) {
|
|
234
|
+
try {
|
|
235
|
+
fn();
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
console.warn(`\nWarning: Couldn't clean up legacy ${label} MCP entry — ${err.message}`);
|
|
239
|
+
console.warn("Continuing with install. The stale entry won't break the CLI but the agent may show a broken-MCP indicator until you remove it manually.");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
216
242
|
const hasClaude = (0, utils_1.claudeCodeInstalled)();
|
|
217
243
|
const hasCodex = (0, utils_1.codexInstalled)();
|
|
218
244
|
const hasCursor = (0, utils_1.cursorInstalled)();
|
|
219
|
-
const hasVSCodeCopilot = (0, utils_1.vscodeCopilotInstalled)();
|
|
220
245
|
const installed = [];
|
|
221
|
-
|
|
246
|
+
// Machine-readable target keys reported back to the backend via
|
|
247
|
+
// /api/cli/markInstalled so the portal's "Connected" state reflects
|
|
248
|
+
// what's actually wired up on this machine, not just "token validated."
|
|
249
|
+
const installedTargets = [];
|
|
222
250
|
if (hasClaude) {
|
|
223
251
|
try {
|
|
224
252
|
(0, utils_1.installHook)();
|
|
225
253
|
installed.push("Claude Code SessionEnd hook (~/.claude/settings.json)");
|
|
254
|
+
installedTargets.push("claudeCode-hook");
|
|
226
255
|
}
|
|
227
256
|
catch (err) {
|
|
228
257
|
console.error(`\nWarning: Failed to install Claude Code hook — ${err.message}`);
|
|
@@ -230,22 +259,17 @@ async function runConnect(args = []) {
|
|
|
230
259
|
try {
|
|
231
260
|
(0, utils_1.installClaudeCodeInstructions)();
|
|
232
261
|
installed.push("Claude Code agent instructions (~/.claude/CLAUDE.md)");
|
|
262
|
+
installedTargets.push("claudeCode-instructions");
|
|
233
263
|
}
|
|
234
264
|
catch (err) {
|
|
235
265
|
console.error(`\nWarning: Failed to install Claude Code instructions — ${err.message}`);
|
|
236
266
|
}
|
|
237
|
-
try {
|
|
238
|
-
(0, utils_1.installClaudeCodeMcp)();
|
|
239
|
-
installed.push("Claude Code MCP server (~/.claude.json)");
|
|
240
|
-
}
|
|
241
|
-
catch (err) {
|
|
242
|
-
console.error(`\nWarning: Failed to install Claude Code MCP server — ${err.message}`);
|
|
243
|
-
}
|
|
244
267
|
}
|
|
245
268
|
if (hasCodex) {
|
|
246
269
|
try {
|
|
247
270
|
(0, utils_1.installCodexHook)();
|
|
248
271
|
installed.push("Codex Stop hook (~/.codex/hooks.json)");
|
|
272
|
+
installedTargets.push("codex-hook");
|
|
249
273
|
}
|
|
250
274
|
catch (err) {
|
|
251
275
|
console.error(`\nWarning: Failed to install Codex hook — ${err.message}`);
|
|
@@ -253,22 +277,17 @@ async function runConnect(args = []) {
|
|
|
253
277
|
try {
|
|
254
278
|
(0, utils_1.installCodexInstructions)();
|
|
255
279
|
installed.push("Codex agent instructions (~/.codex/AGENTS.md)");
|
|
280
|
+
installedTargets.push("codex-instructions");
|
|
256
281
|
}
|
|
257
282
|
catch (err) {
|
|
258
283
|
console.error(`\nWarning: Failed to install Codex instructions — ${err.message}`);
|
|
259
284
|
}
|
|
260
|
-
try {
|
|
261
|
-
(0, utils_1.installCodexMcp)();
|
|
262
|
-
installed.push("Codex MCP server (~/.codex/config.toml)");
|
|
263
|
-
}
|
|
264
|
-
catch (err) {
|
|
265
|
-
console.error(`\nWarning: Failed to install Codex MCP server — ${err.message}`);
|
|
266
|
-
}
|
|
267
285
|
}
|
|
268
286
|
if (hasCursor) {
|
|
269
287
|
try {
|
|
270
288
|
(0, utils_1.installCursorHook)();
|
|
271
289
|
installed.push("Cursor sessionEnd hook (~/.cursor/hooks.json)");
|
|
290
|
+
installedTargets.push("cursor-hook");
|
|
272
291
|
}
|
|
273
292
|
catch (err) {
|
|
274
293
|
console.error(`\nWarning: Failed to install Cursor hook — ${err.message}`);
|
|
@@ -276,30 +295,29 @@ async function runConnect(args = []) {
|
|
|
276
295
|
try {
|
|
277
296
|
(0, utils_1.installCursorInstructions)();
|
|
278
297
|
installed.push("Cursor agent instructions (~/.cursor/rules/codiedev.mdc)");
|
|
298
|
+
installedTargets.push("cursor-instructions");
|
|
279
299
|
}
|
|
280
300
|
catch (err) {
|
|
281
301
|
console.error(`\nWarning: Failed to install Cursor instructions — ${err.message}`);
|
|
282
302
|
}
|
|
283
|
-
try {
|
|
284
|
-
(0, utils_1.installCursorMcp)();
|
|
285
|
-
installed.push("Cursor MCP server (~/.cursor/mcp.json)");
|
|
286
|
-
}
|
|
287
|
-
catch (err) {
|
|
288
|
-
console.error(`\nWarning: Failed to install Cursor MCP server — ${err.message}`);
|
|
289
|
-
}
|
|
290
303
|
}
|
|
291
|
-
|
|
304
|
+
// Tell the backend which targets we wired up so the portal's onboarding
|
|
305
|
+
// card flips to "Connected" only when something actually installed —
|
|
306
|
+
// not on bare token validation. Best-effort: a network failure here
|
|
307
|
+
// doesn't undo the local install, and `codiedev doctor` still works.
|
|
308
|
+
if (installedTargets.length > 0) {
|
|
292
309
|
try {
|
|
293
|
-
(
|
|
294
|
-
|
|
295
|
-
|
|
310
|
+
await postJson(`${BACKEND_URL}/api/cli/markInstalled`, {
|
|
311
|
+
installedTargets,
|
|
312
|
+
}, { Authorization: `Bearer ${token}` });
|
|
296
313
|
}
|
|
297
314
|
catch (err) {
|
|
298
|
-
console.error(`\nWarning: Failed to install
|
|
315
|
+
console.error(`\nWarning: Failed to report install to backend — ${err.message}`);
|
|
316
|
+
console.error("Local install succeeded; portal may not reflect it until you re-run.");
|
|
299
317
|
}
|
|
300
318
|
}
|
|
301
|
-
if (!hasClaude && !hasCodex && !hasCursor
|
|
302
|
-
console.warn("\nNo Claude Code, Codex,
|
|
319
|
+
if (!hasClaude && !hasCodex && !hasCursor) {
|
|
320
|
+
console.warn("\nNo Claude Code, Codex, or Cursor install detected.");
|
|
303
321
|
console.warn("If you just installed Claude Code, launch it once (so ~/.claude is created),");
|
|
304
322
|
console.warn("then re-run `npx codiedev connect` to wire up the capture hook.");
|
|
305
323
|
}
|
|
@@ -312,13 +330,6 @@ async function runConnect(args = []) {
|
|
|
312
330
|
}
|
|
313
331
|
console.log("Sessions will be captured automatically.");
|
|
314
332
|
}
|
|
315
|
-
if (vscodeMcpInstalled) {
|
|
316
|
-
console.log();
|
|
317
|
-
console.log("VS Code: open Copilot Chat — it will prompt you to trust the codiedev MCP server.");
|
|
318
|
-
console.log("Click Allow once, then ask Copilot: \"create a reverse ticket from my current changes\".");
|
|
319
|
-
console.log("Note: Copilot doesn't expose chat transcripts to MCP, so on-demand tools (reverse_ticket,");
|
|
320
|
-
console.log("push, pull, ask, ping) work fully — auto-captured decisions need Claude Code or Codex.");
|
|
321
|
-
}
|
|
322
333
|
console.log();
|
|
323
334
|
console.log("Run `codiedev doctor` to verify everything's wired up.");
|
|
324
335
|
console.log();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill instructions printed to stdout when `codiedev create-ticket` is
|
|
3
|
+
* invoked without `--submit`. Claude (running the CLI via Bash) reads
|
|
4
|
+
* this content and follows the protocol: ask the dev about repos,
|
|
5
|
+
* investigate locally, emit a plan, call back into the CLI to submit.
|
|
6
|
+
*
|
|
7
|
+
* The scoping rules (#### TICKET QUALITY BAR onwards) are lifted verbatim
|
|
8
|
+
* from `modal/runner.py:_format_ticket_from_analysis` so the CLI path
|
|
9
|
+
* produces structurally-identical plans to the Modal path.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CREATE_TICKET_SKILL_INSTRUCTIONS = "\nYou are conducting a forward ticket-writing session for the user via codiedev.\nFollow this protocol exactly. Do not improvise the ticket structure.\n\n## Step 1 \u2014 Ask about repos\nAsk the user once:\n \"Is this work in a single repo or across multiple? If multiple, please tell me where each one is located on disk.\"\nWait for their reply. They will give you absolute paths like /Users/x/code/foo.\n\n## Step 2 \u2014 Investigate (read-only)\nFor each repo path the user gave you:\n - cd into the repo\n - Read the user's intent (their prompt, given below)\n - Find the primary files related to that intent. Use grep for class names,\n function names, enum values, constants, file names extracted from the prompt.\n - For each file found, report what it contains: function signatures, enum\n values, switch/case statements, existing patterns.\n - Grep for who imports each file (consumers, dependencies).\n - Check for existing tests related to those files.\n - Note migration patterns, naming conventions, barrel exports.\nDO NOT modify any files. This is read-only.\n\n## Step 2.5 \u2014 Ask if the prompt doesn't match the codebase\n\nBefore writing the plan, check: do the user's literal words appear in the codebase as named?\n\n- If they said \"delete X\" but only \"archive X\" / \"setArchived\" exists, ask one question quoting\n both sides: \"You said `delete`, but I see only `setArchived` \u2014 confirm the modal is for the\n archive button?\"\n- If they said \"the analytics dashboard\" but the named component (e.g. AnalyticsTab) is already\n reactive and the only useEffect-fetch lives elsewhere, ask: \"AnalyticsTab is already on\n useQuery; the only useEffect-fetch I found is in admin/page.tsx LangfuseCostsSection \u2014 is\n that the target?\"\n- If they named a feature/flag/component that doesn't exist at all, ask which of the closest\n matches they meant, listing each with a one-line reason it's plausible.\n\nDo NOT silently route to the closest match in these cases. Silently routing buries the mismatch\nin scope OUT and produces a ticket the user didn't ask for. One targeted question that quotes\nthe user's word AND the codebase's actual word is the right move.\n\nIf you also have a separate genuine ambiguity (two plausible places the change could go that\nboth match the user's words), bundle it into the same question \u2014 but don't fabricate ambiguity\nto hit a quota. If the words map to the codebase clearly, do not ask further questions \u2014\nproceed to Step 3.\n\n## Step 3 \u2014 Format the plan\n\nThe user's request defines WHAT to build. The investigation defines HOW to build it.\nYour job is to translate the user's exact request into actionable tickets using\nthe investigation as supporting evidence. The user chose their words carefully \u2014\nuse them.\n\nOutput a plan as one or more tickets. Each ticket becomes a single PR handled\nby a coding agent. The agent works alone and makes one PR per ticket.\n\nEACH TICKET = ONE MERGEABLE PR\nA ticket is ready when its PR can merge to main and the app compiles, renders,\nand passes tests with zero other tickets merged. This is the only rule that matters.\n\nHow to group work:\n- The investigation identified every file that needs to change. Your job is to\n package that into tickets, not to re-scope. Every file from the investigation\n appears in at least one ticket.\n- Each ticket becomes one PR. A PR should be focused enough that a reviewer\n understands it in one sitting and CI validates it fully.\n- Include every file needed for the change to be complete \u2014 don't leave wiring,\n migrations, or consumer updates for a follow-up.\n- If the work naturally spans independent modules, output multiple tickets ordered\n by dependency. Use the `dependsOn` field (array of zero-indexed ticket numbers\n this ticket depends on).\n- \"After this PR merges, the app:\" must describe a working state. If you can't,\n the ticket is incomplete \u2014 add what's missing.\n- Default to ONE ticket. Only split when ticket 2 could merge to main without\n ticket 1 and the app still compiles.\n\nQUALITY BAR \u2014 every ticket must:\n- Quantify the problem: count the calls, files, violations. \"6 manager.getRepository()\n calls\", not \"several calls\".\n- Name the codebase's own patterns: use the exact class names, method names, and\n conventions found in the investigation. \"injectable wrapper pattern with\n @InjectRepository\", not \"create a new class\".\n- Include specific file paths with line numbers from the investigation findings.\n- Say what stays the same: \"Keep standard CRUD in AceService\", \"do not export it\",\n \"no logic changes\".\n- Scope IN/OUT must reference concrete things in the codebase, not abstract categories.\n\nFor each ticket, fill these fields:\n - title: <80 chars, imperative mood\n - description: GIVEN/WHEN/THEN format, quantify the problem\n - technicalDetails: specific files with line numbers, the approach,\n patterns from the codebase to follow, what to keep unchanged\n - scope: IN: ... / OUT: ...\n - targetRepoId: leave empty for now \u2014 the CLI will fill this from the repo paths\n - targetRepoFullName: \"owner/name\" matching the git remote of the repo this\n ticket touches (e.g. \"ericlam1114/vm-demo\")\n - dependsOn: [zero-indexed ticket numbers] or [] if independent\n\n## Step 4 \u2014 Submit\nWhen the plan is ready, write it to a temp file as JSON matching this schema:\n\n{\n \"plan\": {\n \"summary\": \"<one sentence: what this plan delivers and how many tickets>\",\n \"tickets\": [\n {\n \"title\": \"...\",\n \"description\": \"...\",\n \"technicalDetails\": \"...\",\n \"scope\": \"...\",\n \"targetRepoFullName\": \"owner/name\",\n \"dependsOn\": []\n }\n ]\n },\n \"prompt\": \"<the user's original intent verbatim>\",\n \"repoFullNames\": [\"owner/name\", ...]\n}\n\nThen run:\n codiedev create-ticket --submit <path-to-temp-file.json>\n\nThe CLI will translate `targetRepoFullName` and `repoFullNames` into repo IDs,\nPOST the plan, and print the portal URL. Tell the user the URL when it returns.\n\n## The user's intent\n{{USER_PROMPT}}\n";
|
|
12
|
+
export declare function renderSkillInstructions(prompt: string | undefined): string;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CREATE_TICKET_SKILL_INSTRUCTIONS = void 0;
|
|
4
|
+
exports.renderSkillInstructions = renderSkillInstructions;
|
|
5
|
+
/**
|
|
6
|
+
* Skill instructions printed to stdout when `codiedev create-ticket` is
|
|
7
|
+
* invoked without `--submit`. Claude (running the CLI via Bash) reads
|
|
8
|
+
* this content and follows the protocol: ask the dev about repos,
|
|
9
|
+
* investigate locally, emit a plan, call back into the CLI to submit.
|
|
10
|
+
*
|
|
11
|
+
* The scoping rules (#### TICKET QUALITY BAR onwards) are lifted verbatim
|
|
12
|
+
* from `modal/runner.py:_format_ticket_from_analysis` so the CLI path
|
|
13
|
+
* produces structurally-identical plans to the Modal path.
|
|
14
|
+
*/
|
|
15
|
+
exports.CREATE_TICKET_SKILL_INSTRUCTIONS = `
|
|
16
|
+
You are conducting a forward ticket-writing session for the user via codiedev.
|
|
17
|
+
Follow this protocol exactly. Do not improvise the ticket structure.
|
|
18
|
+
|
|
19
|
+
## Step 1 — Ask about repos
|
|
20
|
+
Ask the user once:
|
|
21
|
+
"Is this work in a single repo or across multiple? If multiple, please tell me where each one is located on disk."
|
|
22
|
+
Wait for their reply. They will give you absolute paths like /Users/x/code/foo.
|
|
23
|
+
|
|
24
|
+
## Step 2 — Investigate (read-only)
|
|
25
|
+
For each repo path the user gave you:
|
|
26
|
+
- cd into the repo
|
|
27
|
+
- Read the user's intent (their prompt, given below)
|
|
28
|
+
- Find the primary files related to that intent. Use grep for class names,
|
|
29
|
+
function names, enum values, constants, file names extracted from the prompt.
|
|
30
|
+
- For each file found, report what it contains: function signatures, enum
|
|
31
|
+
values, switch/case statements, existing patterns.
|
|
32
|
+
- Grep for who imports each file (consumers, dependencies).
|
|
33
|
+
- Check for existing tests related to those files.
|
|
34
|
+
- Note migration patterns, naming conventions, barrel exports.
|
|
35
|
+
DO NOT modify any files. This is read-only.
|
|
36
|
+
|
|
37
|
+
## Step 2.5 — Ask if the prompt doesn't match the codebase
|
|
38
|
+
|
|
39
|
+
Before writing the plan, check: do the user's literal words appear in the codebase as named?
|
|
40
|
+
|
|
41
|
+
- If they said "delete X" but only "archive X" / "setArchived" exists, ask one question quoting
|
|
42
|
+
both sides: "You said \`delete\`, but I see only \`setArchived\` — confirm the modal is for the
|
|
43
|
+
archive button?"
|
|
44
|
+
- If they said "the analytics dashboard" but the named component (e.g. AnalyticsTab) is already
|
|
45
|
+
reactive and the only useEffect-fetch lives elsewhere, ask: "AnalyticsTab is already on
|
|
46
|
+
useQuery; the only useEffect-fetch I found is in admin/page.tsx LangfuseCostsSection — is
|
|
47
|
+
that the target?"
|
|
48
|
+
- If they named a feature/flag/component that doesn't exist at all, ask which of the closest
|
|
49
|
+
matches they meant, listing each with a one-line reason it's plausible.
|
|
50
|
+
|
|
51
|
+
Do NOT silently route to the closest match in these cases. Silently routing buries the mismatch
|
|
52
|
+
in scope OUT and produces a ticket the user didn't ask for. One targeted question that quotes
|
|
53
|
+
the user's word AND the codebase's actual word is the right move.
|
|
54
|
+
|
|
55
|
+
If you also have a separate genuine ambiguity (two plausible places the change could go that
|
|
56
|
+
both match the user's words), bundle it into the same question — but don't fabricate ambiguity
|
|
57
|
+
to hit a quota. If the words map to the codebase clearly, do not ask further questions —
|
|
58
|
+
proceed to Step 3.
|
|
59
|
+
|
|
60
|
+
## Step 3 — Format the plan
|
|
61
|
+
|
|
62
|
+
The user's request defines WHAT to build. The investigation defines HOW to build it.
|
|
63
|
+
Your job is to translate the user's exact request into actionable tickets using
|
|
64
|
+
the investigation as supporting evidence. The user chose their words carefully —
|
|
65
|
+
use them.
|
|
66
|
+
|
|
67
|
+
Output a plan as one or more tickets. Each ticket becomes a single PR handled
|
|
68
|
+
by a coding agent. The agent works alone and makes one PR per ticket.
|
|
69
|
+
|
|
70
|
+
EACH TICKET = ONE MERGEABLE PR
|
|
71
|
+
A ticket is ready when its PR can merge to main and the app compiles, renders,
|
|
72
|
+
and passes tests with zero other tickets merged. This is the only rule that matters.
|
|
73
|
+
|
|
74
|
+
How to group work:
|
|
75
|
+
- The investigation identified every file that needs to change. Your job is to
|
|
76
|
+
package that into tickets, not to re-scope. Every file from the investigation
|
|
77
|
+
appears in at least one ticket.
|
|
78
|
+
- Each ticket becomes one PR. A PR should be focused enough that a reviewer
|
|
79
|
+
understands it in one sitting and CI validates it fully.
|
|
80
|
+
- Include every file needed for the change to be complete — don't leave wiring,
|
|
81
|
+
migrations, or consumer updates for a follow-up.
|
|
82
|
+
- If the work naturally spans independent modules, output multiple tickets ordered
|
|
83
|
+
by dependency. Use the \`dependsOn\` field (array of zero-indexed ticket numbers
|
|
84
|
+
this ticket depends on).
|
|
85
|
+
- "After this PR merges, the app:" must describe a working state. If you can't,
|
|
86
|
+
the ticket is incomplete — add what's missing.
|
|
87
|
+
- Default to ONE ticket. Only split when ticket 2 could merge to main without
|
|
88
|
+
ticket 1 and the app still compiles.
|
|
89
|
+
|
|
90
|
+
QUALITY BAR — every ticket must:
|
|
91
|
+
- Quantify the problem: count the calls, files, violations. "6 manager.getRepository()
|
|
92
|
+
calls", not "several calls".
|
|
93
|
+
- Name the codebase's own patterns: use the exact class names, method names, and
|
|
94
|
+
conventions found in the investigation. "injectable wrapper pattern with
|
|
95
|
+
@InjectRepository", not "create a new class".
|
|
96
|
+
- Include specific file paths with line numbers from the investigation findings.
|
|
97
|
+
- Say what stays the same: "Keep standard CRUD in AceService", "do not export it",
|
|
98
|
+
"no logic changes".
|
|
99
|
+
- Scope IN/OUT must reference concrete things in the codebase, not abstract categories.
|
|
100
|
+
|
|
101
|
+
For each ticket, fill these fields:
|
|
102
|
+
- title: <80 chars, imperative mood
|
|
103
|
+
- description: GIVEN/WHEN/THEN format, quantify the problem
|
|
104
|
+
- technicalDetails: specific files with line numbers, the approach,
|
|
105
|
+
patterns from the codebase to follow, what to keep unchanged
|
|
106
|
+
- scope: IN: ... / OUT: ...
|
|
107
|
+
- targetRepoId: leave empty for now — the CLI will fill this from the repo paths
|
|
108
|
+
- targetRepoFullName: "owner/name" matching the git remote of the repo this
|
|
109
|
+
ticket touches (e.g. "ericlam1114/vm-demo")
|
|
110
|
+
- dependsOn: [zero-indexed ticket numbers] or [] if independent
|
|
111
|
+
|
|
112
|
+
## Step 4 — Submit
|
|
113
|
+
When the plan is ready, write it to a temp file as JSON matching this schema:
|
|
114
|
+
|
|
115
|
+
{
|
|
116
|
+
"plan": {
|
|
117
|
+
"summary": "<one sentence: what this plan delivers and how many tickets>",
|
|
118
|
+
"tickets": [
|
|
119
|
+
{
|
|
120
|
+
"title": "...",
|
|
121
|
+
"description": "...",
|
|
122
|
+
"technicalDetails": "...",
|
|
123
|
+
"scope": "...",
|
|
124
|
+
"targetRepoFullName": "owner/name",
|
|
125
|
+
"dependsOn": []
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
"prompt": "<the user's original intent verbatim>",
|
|
130
|
+
"repoFullNames": ["owner/name", ...]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Then run:
|
|
134
|
+
codiedev create-ticket --submit <path-to-temp-file.json>
|
|
135
|
+
|
|
136
|
+
The CLI will translate \`targetRepoFullName\` and \`repoFullNames\` into repo IDs,
|
|
137
|
+
POST the plan, and print the portal URL. Tell the user the URL when it returns.
|
|
138
|
+
|
|
139
|
+
## The user's intent
|
|
140
|
+
{{USER_PROMPT}}
|
|
141
|
+
`;
|
|
142
|
+
function renderSkillInstructions(prompt) {
|
|
143
|
+
return exports.CREATE_TICKET_SKILL_INSTRUCTIONS.replace("{{USER_PROMPT}}", prompt && prompt.length > 0
|
|
144
|
+
? prompt
|
|
145
|
+
: "(not yet provided — ask the user a single combined question that covers both the intent AND repo locations: \"What are you working on, and is this in a single repo or multiple? If multiple, give me the paths.\" Skip Step 1 since you've folded it into this question. Then proceed to Step 2.)");
|
|
146
|
+
}
|
package/dist/detection.d.ts
CHANGED
|
@@ -10,3 +10,21 @@ export interface ClaudeCodeProbe {
|
|
|
10
10
|
* obviously committed to the tool.
|
|
11
11
|
*/
|
|
12
12
|
export declare function detectClaudeCode(probe: ClaudeCodeProbe): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* True iff `cmd` looks like a hook command codiedev itself wrote. Used
|
|
15
|
+
* both by the install filter (so `connect` replaces the existing entry
|
|
16
|
+
* instead of stacking another one) and by `codiedev doctor` (so it can
|
|
17
|
+
* grade whether the hook is wired up).
|
|
18
|
+
*
|
|
19
|
+
* Must match every form codiedev can write:
|
|
20
|
+
* - Legacy `npx codiedev-hook <subcommand>`
|
|
21
|
+
* - Absolute path from npm-registry install:
|
|
22
|
+
* `<node> /.../node_modules/codiedev/dist/hook.js <subcommand>`
|
|
23
|
+
* - Absolute path from `npm link` dev install (the monorepo folder
|
|
24
|
+
* is named `codiedev-cli`, not `codiedev`):
|
|
25
|
+
* `<node> /.../codiedev-cli/dist/hook.js <subcommand>`
|
|
26
|
+
*
|
|
27
|
+
* The `codiedev[^/\\]*` allows the trailing `-cli` (or any future suffix)
|
|
28
|
+
* without matching unrelated paths like `notcodiedev/dist/hook.js`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isCodiedevHookCommand(cmd: string | undefined): boolean;
|
package/dist/detection.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// mocking fs or process.
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.detectClaudeCode = detectClaudeCode;
|
|
7
|
+
exports.isCodiedevHookCommand = isCodiedevHookCommand;
|
|
7
8
|
/**
|
|
8
9
|
* Claude Code is "installed" if either ~/.claude exists (the user has
|
|
9
10
|
* launched the app at least once) OR the `claude` binary is on PATH (the
|
|
@@ -14,3 +15,30 @@ exports.detectClaudeCode = detectClaudeCode;
|
|
|
14
15
|
function detectClaudeCode(probe) {
|
|
15
16
|
return probe.dirExists || probe.binOnPath;
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* True iff `cmd` looks like a hook command codiedev itself wrote. Used
|
|
20
|
+
* both by the install filter (so `connect` replaces the existing entry
|
|
21
|
+
* instead of stacking another one) and by `codiedev doctor` (so it can
|
|
22
|
+
* grade whether the hook is wired up).
|
|
23
|
+
*
|
|
24
|
+
* Must match every form codiedev can write:
|
|
25
|
+
* - Legacy `npx codiedev-hook <subcommand>`
|
|
26
|
+
* - Absolute path from npm-registry install:
|
|
27
|
+
* `<node> /.../node_modules/codiedev/dist/hook.js <subcommand>`
|
|
28
|
+
* - Absolute path from `npm link` dev install (the monorepo folder
|
|
29
|
+
* is named `codiedev-cli`, not `codiedev`):
|
|
30
|
+
* `<node> /.../codiedev-cli/dist/hook.js <subcommand>`
|
|
31
|
+
*
|
|
32
|
+
* The `codiedev[^/\\]*` allows the trailing `-cli` (or any future suffix)
|
|
33
|
+
* without matching unrelated paths like `notcodiedev/dist/hook.js`.
|
|
34
|
+
*/
|
|
35
|
+
function isCodiedevHookCommand(cmd) {
|
|
36
|
+
if (!cmd)
|
|
37
|
+
return false;
|
|
38
|
+
if (cmd.includes("codiedev-hook"))
|
|
39
|
+
return true;
|
|
40
|
+
// Anchor at a path separator (or string start) so we don't match
|
|
41
|
+
// unrelated paths like `notcodiedev/dist/hook.js` that happen to contain
|
|
42
|
+
// the substring `codiedev`.
|
|
43
|
+
return /(?:^|[\\/])codiedev[^/\\]*[\\/]dist[\\/]hook/.test(cmd);
|
|
44
|
+
}
|