@cargo-ai/cli 1.0.78 → 1.0.80

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.
Files changed (67) hide show
  1. package/README.md +12 -0
  2. package/build/commands/ask/argvFlags.d.ts +18 -0
  3. package/build/commands/ask/argvFlags.d.ts.map +1 -0
  4. package/build/commands/ask/argvFlags.js +46 -0
  5. package/build/commands/ask/artifacts.d.ts +2 -0
  6. package/build/commands/ask/artifacts.d.ts.map +1 -0
  7. package/build/commands/ask/artifacts.js +100 -0
  8. package/build/commands/ask/binding.d.ts +13 -0
  9. package/build/commands/ask/binding.d.ts.map +1 -0
  10. package/build/commands/ask/binding.js +19 -0
  11. package/build/commands/ask/claudeConfig.d.ts +23 -0
  12. package/build/commands/ask/claudeConfig.d.ts.map +1 -0
  13. package/build/commands/ask/claudeConfig.js +33 -0
  14. package/build/commands/ask/completer.d.ts +48 -0
  15. package/build/commands/ask/completer.d.ts.map +1 -0
  16. package/build/commands/ask/completer.js +217 -0
  17. package/build/commands/ask/history.d.ts +32 -0
  18. package/build/commands/ask/history.d.ts.map +1 -0
  19. package/build/commands/ask/history.js +122 -0
  20. package/build/commands/ask/index.d.ts +14 -0
  21. package/build/commands/ask/index.d.ts.map +1 -0
  22. package/build/commands/ask/index.js +111 -0
  23. package/build/commands/ask/localHarness.d.ts +225 -0
  24. package/build/commands/ask/localHarness.d.ts.map +1 -0
  25. package/build/commands/ask/localHarness.js +1074 -0
  26. package/build/commands/ask/messageStream.d.ts +33 -0
  27. package/build/commands/ask/messageStream.d.ts.map +1 -0
  28. package/build/commands/ask/messageStream.js +114 -0
  29. package/build/commands/ask/persist.d.ts +5 -0
  30. package/build/commands/ask/persist.d.ts.map +1 -0
  31. package/build/commands/ask/persist.js +31 -0
  32. package/build/commands/ask/pnpmShim.d.ts +20 -0
  33. package/build/commands/ask/pnpmShim.d.ts.map +1 -0
  34. package/build/commands/ask/pnpmShim.js +39 -0
  35. package/build/commands/ask/prompt.d.ts +20 -0
  36. package/build/commands/ask/prompt.d.ts.map +1 -0
  37. package/build/commands/ask/prompt.js +228 -0
  38. package/build/commands/ask/rewriteChatFlag.d.ts +9 -0
  39. package/build/commands/ask/rewriteChatFlag.d.ts.map +1 -0
  40. package/build/commands/ask/rewriteChatFlag.js +26 -0
  41. package/build/commands/ask/session.d.ts +94 -0
  42. package/build/commands/ask/session.d.ts.map +1 -0
  43. package/build/commands/ask/session.js +965 -0
  44. package/build/commands/ask/slash.d.ts +20 -0
  45. package/build/commands/ask/slash.d.ts.map +1 -0
  46. package/build/commands/ask/slash.js +73 -0
  47. package/build/commands/ask/state.d.ts +9 -0
  48. package/build/commands/ask/state.d.ts.map +1 -0
  49. package/build/commands/ask/state.js +18 -0
  50. package/build/commands/bookDemo.d.ts +14 -0
  51. package/build/commands/bookDemo.d.ts.map +1 -0
  52. package/build/commands/bookDemo.js +100 -0
  53. package/build/commands/runHandler.d.ts +9 -1
  54. package/build/commands/runHandler.d.ts.map +1 -1
  55. package/build/commands/runHandler.js +16 -1
  56. package/build/index.js +56 -18
  57. package/build/paletteLayout.d.ts +6 -1
  58. package/build/paletteLayout.d.ts.map +1 -1
  59. package/build/paletteLayout.js +35 -9
  60. package/build/utils/cdkState.d.ts +17 -4
  61. package/build/utils/cdkState.d.ts.map +1 -1
  62. package/build/utils/cdkState.js +26 -4
  63. package/build/utils/loadCompiledAgent.d.ts.map +1 -1
  64. package/build/utils/loadCompiledAgent.js +51 -11
  65. package/build/utils/prompt.d.ts.map +1 -1
  66. package/build/utils/prompt.js +4 -1
  67. package/package.json +8 -4
package/README.md CHANGED
@@ -54,6 +54,18 @@ cargo-ai whoami
54
54
  cargo-ai logout
55
55
  ```
56
56
 
57
+ ### Book a demo
58
+
59
+ `cargo-ai book-demo` opens the same Cal.com form as the website's **Talk to sales** button. A person still has to pick a time — the command does not create a booking. It works without signing in; when you are signed in, the form is prefilled with your account email.
60
+
61
+ ```bash
62
+ cargo-ai book-demo
63
+ cargo-ai book-demo --email you@company.com
64
+ cargo-ai book-demo --no-open # print the URL only
65
+ ```
66
+
67
+ This is not the guided product tour. That is `cargo-ai start --continue demo`.
68
+
57
69
  ### Sign-in without a browser (`--email`)
58
70
 
59
71
  `cargo-ai login --email you@company.com` emails you a one-time code, then creates your account (on first use) and a workspace — no browser and no dashboard visit at any point. On a terminal the code is read back from a prompt. Where there is no terminal (sandboxes, agent shells) the first call sends the code and exits, so you can finish with:
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Stop Ask's boolean flags from eating the question.
3
+ *
4
+ * They are declared `--flag [true|false]` so `--flag false` works and the
5
+ * palette can offer the two values as a menu. Commander reads an optional-value
6
+ * option greedily, though: in `ask --print "summarise this"` the message
7
+ * becomes the flag's value and `parseBooleanOption` rejects it, so the
8
+ * documented form fails at parse time.
9
+ *
10
+ * Writing the value inline (`--print=true`) settles the option before Commander
11
+ * looks at the next token, so the message stays a positional argument. Only
12
+ * done when the next token is not a boolean, which leaves `--print false` and
13
+ * `--print=false` exactly as the caller typed them.
14
+ *
15
+ * Exported for tests; `index.ts` is the only production caller.
16
+ */
17
+ export declare function pinAskBooleanFlags(argv: readonly string[]): string[];
18
+ //# sourceMappingURL=argvFlags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"argvFlags.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/argvFlags.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAoCpE"}
@@ -0,0 +1,46 @@
1
+ /** Ask's boolean flags, declared as `--flag [true|false]`. */
2
+ const ASK_BOOLEAN_FLAGS = ["--print", "--continue"];
3
+ /**
4
+ * Stop Ask's boolean flags from eating the question.
5
+ *
6
+ * They are declared `--flag [true|false]` so `--flag false` works and the
7
+ * palette can offer the two values as a menu. Commander reads an optional-value
8
+ * option greedily, though: in `ask --print "summarise this"` the message
9
+ * becomes the flag's value and `parseBooleanOption` rejects it, so the
10
+ * documented form fails at parse time.
11
+ *
12
+ * Writing the value inline (`--print=true`) settles the option before Commander
13
+ * looks at the next token, so the message stays a positional argument. Only
14
+ * done when the next token is not a boolean, which leaves `--print false` and
15
+ * `--print=false` exactly as the caller typed them.
16
+ *
17
+ * Exported for tests; `index.ts` is the only production caller.
18
+ */
19
+ export function pinAskBooleanFlags(argv) {
20
+ const prefix = argv.slice(0, 2);
21
+ const rest = argv.slice(2);
22
+ if (rest[0] !== "ask") {
23
+ return [...argv];
24
+ }
25
+ const pinned = [];
26
+ for (let index = 0; index < rest.length; index += 1) {
27
+ const arg = rest[index];
28
+ if (arg === undefined) {
29
+ continue;
30
+ }
31
+ // Everything after `--` is a positional the caller quoted deliberately.
32
+ if (arg === "--") {
33
+ pinned.push(...rest.slice(index));
34
+ break;
35
+ }
36
+ if (ASK_BOOLEAN_FLAGS.includes(arg) === true) {
37
+ const next = rest[index + 1];
38
+ if (next !== "true" && next !== "false") {
39
+ pinned.push(`${arg}=true`);
40
+ continue;
41
+ }
42
+ }
43
+ pinned.push(arg);
44
+ }
45
+ return [...prefix, ...pinned];
46
+ }
@@ -0,0 +1,2 @@
1
+ export declare function pruneHarnessArtifacts(dir: string, now?: number): void;
2
+ //# sourceMappingURL=artifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/artifacts.ts"],"names":[],"mappings":"AAwBA,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,IAAI,CAGzE"}
@@ -0,0 +1,100 @@
1
+ import { readdirSync, rmdirSync, rmSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /**
4
+ * Every local turn writes bridge state into the directory Ask runs from —
5
+ * `.agent-runs/<sessionId>/bridge` carries the event log and the start config,
6
+ * and the start config carries the proxy token — and nothing upstream ever
7
+ * removes it. Left alone, a working copy fills with dead sessions and keeps
8
+ * spent tokens on disk long after the chat that minted them.
9
+ *
10
+ * A run is kept while a chat could still reattach to it, and swept once it is
11
+ * older than that. `claude-code-<sessionId>` directories are the per-session
12
+ * work directories the harness derives, which local sessions leave empty; only
13
+ * an empty, aged-out one is removed, so anything a session actually put there
14
+ * — or is using now — survives.
15
+ *
16
+ * Best-effort throughout: a directory that is locked, half-written, or being
17
+ * used by another Ask process is skipped rather than allowed to take the
18
+ * session down with it.
19
+ */
20
+ const RUN_RETENTION_MS = 3 * 24 * 60 * 60 * 1000;
21
+ const WORK_DIR_PREFIX = "claude-code-";
22
+ export function pruneHarnessArtifacts(dir, now = Date.now()) {
23
+ removeStaleRuns(join(dir, ".agent-runs"), now);
24
+ removeEmptyWorkDirs(dir, now);
25
+ }
26
+ function removeStaleRuns(runsDir, now) {
27
+ let entries;
28
+ try {
29
+ entries = readdirSync(runsDir);
30
+ }
31
+ catch {
32
+ return;
33
+ }
34
+ for (const entry of entries) {
35
+ const path = join(runsDir, entry);
36
+ try {
37
+ if (now - newestMtimeMs(path) < RUN_RETENTION_MS) {
38
+ continue;
39
+ }
40
+ rmSync(path, { recursive: true, force: true });
41
+ }
42
+ catch {
43
+ continue;
44
+ }
45
+ }
46
+ try {
47
+ rmdirSync(runsDir);
48
+ }
49
+ catch {
50
+ // Runs we kept are still in there, which is the ordinary case.
51
+ }
52
+ }
53
+ function removeEmptyWorkDirs(dir, now) {
54
+ let entries;
55
+ try {
56
+ entries = readdirSync(dir);
57
+ }
58
+ catch {
59
+ return;
60
+ }
61
+ for (const entry of entries) {
62
+ if (entry.startsWith(WORK_DIR_PREFIX) === false) {
63
+ continue;
64
+ }
65
+ const path = join(dir, entry);
66
+ try {
67
+ // An empty directory's mtime is when it was created, so age is what
68
+ // separates a dead session from one another Ask process is working in
69
+ // right now — that one is its agent's cwd and must survive.
70
+ if (now - statSync(path).mtimeMs < RUN_RETENTION_MS) {
71
+ continue;
72
+ }
73
+ rmdirSync(path);
74
+ }
75
+ catch {
76
+ // Not empty, or not ours to remove.
77
+ continue;
78
+ }
79
+ }
80
+ }
81
+ /**
82
+ * A run directory's own mtime only moves when entries are added or removed, so
83
+ * a session that streamed into an existing event log for an hour can still look
84
+ * untouched. The newest mtime underneath it is what says when the run was last
85
+ * alive.
86
+ */
87
+ function newestMtimeMs(path) {
88
+ const stats = statSync(path);
89
+ if (stats.isDirectory() === false) {
90
+ return stats.mtimeMs;
91
+ }
92
+ let newest = stats.mtimeMs;
93
+ for (const entry of readdirSync(path)) {
94
+ const candidate = newestMtimeMs(join(path, entry));
95
+ if (candidate > newest) {
96
+ newest = candidate;
97
+ }
98
+ }
99
+ return newest;
100
+ }
@@ -0,0 +1,13 @@
1
+ type RepositoryLike = {
2
+ connectorUuid?: string;
3
+ repository?: string;
4
+ } | null | undefined;
5
+ /**
6
+ * A usable GitHub workspace: connector + owner/name. Matches
7
+ * parseHarnessBinding's required fields, without decrypting env.
8
+ *
9
+ * Exported for tests.
10
+ */
11
+ export declare function isBoundRepository(repository: RepositoryLike): boolean;
12
+ export {};
13
+ //# sourceMappingURL=binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/binding.ts"],"names":[],"mappings":"AAAA,KAAK,cAAc,GACf;IACE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACD,IAAI,GACJ,SAAS,CAAC;AAEd;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAgBrE"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A usable GitHub workspace: connector + owner/name. Matches
3
+ * parseHarnessBinding's required fields, without decrypting env.
4
+ *
5
+ * Exported for tests.
6
+ */
7
+ export function isBoundRepository(repository) {
8
+ if (repository === undefined || repository === null) {
9
+ return false;
10
+ }
11
+ if (repository.connectorUuid === undefined) {
12
+ return false;
13
+ }
14
+ if (repository.repository === undefined) {
15
+ return false;
16
+ }
17
+ const slashIndex = repository.repository.indexOf("/");
18
+ return slashIndex > 0 && slashIndex < repository.repository.length - 1;
19
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The Claude Agent SDK loads every filesystem settings source it can find when
3
+ * `settingSources` is omitted, and the harness bridge omits it. A local turn
4
+ * therefore inherits the user's `~/.claude` setup — hooks, plugins, and every
5
+ * globally configured MCP server — and Claude Code blocks on all of it before
6
+ * the model takes its first step. Because a turn relaunches Claude Code, that
7
+ * is paid per message, not per session: on one machine it was 14 seconds of a
8
+ * 22 second turn, most of it a SessionStart hook running a global npm install
9
+ * and a skills sync.
10
+ *
11
+ * Pointing Claude Code at a directory Ask owns keeps a turn's cost to what the
12
+ * turn needs. Project settings and CLAUDE.md resolve from the working
13
+ * directory rather than here, so anything the repo commits still applies.
14
+ *
15
+ * The directory persists between runs: Claude Code keeps its own state and
16
+ * session transcripts under it, and a fresh one each turn would pay first-run
17
+ * costs again.
18
+ *
19
+ * Set `CLAUDE_CONFIG_DIR` to share a config with the `claude` CLI instead —
20
+ * that reinstates the user's hooks and MCP servers, and their startup cost.
21
+ */
22
+ export declare function askClaudeConfigDir(): string;
23
+ //# sourceMappingURL=claudeConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudeConfig.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/claudeConfig.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAY3C"}
@@ -0,0 +1,33 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getConfigDir } from "@cargo-ai/cdk/cli";
4
+ /**
5
+ * The Claude Agent SDK loads every filesystem settings source it can find when
6
+ * `settingSources` is omitted, and the harness bridge omits it. A local turn
7
+ * therefore inherits the user's `~/.claude` setup — hooks, plugins, and every
8
+ * globally configured MCP server — and Claude Code blocks on all of it before
9
+ * the model takes its first step. Because a turn relaunches Claude Code, that
10
+ * is paid per message, not per session: on one machine it was 14 seconds of a
11
+ * 22 second turn, most of it a SessionStart hook running a global npm install
12
+ * and a skills sync.
13
+ *
14
+ * Pointing Claude Code at a directory Ask owns keeps a turn's cost to what the
15
+ * turn needs. Project settings and CLAUDE.md resolve from the working
16
+ * directory rather than here, so anything the repo commits still applies.
17
+ *
18
+ * The directory persists between runs: Claude Code keeps its own state and
19
+ * session transcripts under it, and a fresh one each turn would pay first-run
20
+ * costs again.
21
+ *
22
+ * Set `CLAUDE_CONFIG_DIR` to share a config with the `claude` CLI instead —
23
+ * that reinstates the user's hooks and MCP servers, and their startup cost.
24
+ */
25
+ export function askClaudeConfigDir() {
26
+ const configured = process.env["CLAUDE_CONFIG_DIR"];
27
+ if (configured !== undefined && configured !== "") {
28
+ return configured;
29
+ }
30
+ const dir = join(getConfigDir(), "ask-claude-config");
31
+ mkdirSync(dir, { recursive: true });
32
+ return dir;
33
+ }
@@ -0,0 +1,48 @@
1
+ /** Chat slash commands offered on Tab when the line is `/…`. */
2
+ export declare const ASK_SLASH_COMMANDS: readonly ["/help", "/clear", "/new", "/agent", "/cancel", "/exit"];
3
+ export type AskCompleter = (line: string) => [string[], string];
4
+ /** What a `/…` token turned out to be. */
5
+ export type SlashTokenKind = "command" | "path" | "unknown";
6
+ /**
7
+ * Decide whether a `/…` token is a chat command, a filesystem path, or
8
+ * neither.
9
+ *
10
+ * The completer and the slash parser both need this answer, and they must
11
+ * agree: anything the completer offers has to be accepted when the user
12
+ * presses Enter on it. Written twice, the two drifted — `/tmp` completed as a
13
+ * path and was then answered with "is not a chat command".
14
+ *
15
+ * Shape settles most tokens, but a bare `/word` is ambiguous: `/tmp` and
16
+ * `/orchestration` are spelled the same. Only the filesystem separates them,
17
+ * so an unknown word is looked up — cheap, and reached only for a single
18
+ * bare word that no command matches.
19
+ */
20
+ export declare function classifySlashToken(token: string): SlashTokenKind;
21
+ /** Max rows shown in the live Ask suggestion menu. */
22
+ export declare const ASK_COMPLETION_MENU_LIMIT = 8;
23
+ /**
24
+ * Tab completer for the Ask prompt: slash commands when the line is `/…`,
25
+ * otherwise local path completion for a path-like last token.
26
+ */
27
+ export declare function createAskCompleter(cwd: string): AskCompleter;
28
+ /**
29
+ * Matches for the live suggestion menu (slash commands, else path hits).
30
+ * Empty when the line is plain prose.
31
+ */
32
+ export declare function listCompletions(line: string, cwd: string): string[];
33
+ /**
34
+ * Replace the completed span with `completion`: whole line for slash
35
+ * commands, last token for paths.
36
+ */
37
+ export declare function applyCompletion(line: string, completion: string): string;
38
+ /**
39
+ * `/hel` → `/help`. Only when the whole line is a single `/token` (no spaces)
40
+ * and it matches a chat command prefix. `/tmp` falls through to path completion.
41
+ */
42
+ export declare function completeSlash(line: string): [string[], string] | undefined;
43
+ /**
44
+ * Complete the last whitespace-separated token when it looks like a path.
45
+ * Exported for tests.
46
+ */
47
+ export declare function completePath(line: string, cwd: string): [string[], string];
48
+ //# sourceMappingURL=completer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completer.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/completer.ts"],"names":[],"mappings":"AAYA,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,oEAOrB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC;AAEhE,0CAA0C;AAC1C,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAgBhE;AAED,sDAAsD;AACtD,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAU5D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CASnE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAYxE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,GAAG,SAAS,CAY1E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAwE1E"}
@@ -0,0 +1,217 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from "node:path";
4
+ /** Chat slash commands offered on Tab when the line is `/…`. */
5
+ export const ASK_SLASH_COMMANDS = [
6
+ "/help",
7
+ "/clear",
8
+ "/new",
9
+ "/agent",
10
+ "/cancel",
11
+ "/exit",
12
+ ];
13
+ /**
14
+ * Decide whether a `/…` token is a chat command, a filesystem path, or
15
+ * neither.
16
+ *
17
+ * The completer and the slash parser both need this answer, and they must
18
+ * agree: anything the completer offers has to be accepted when the user
19
+ * presses Enter on it. Written twice, the two drifted — `/tmp` completed as a
20
+ * path and was then answered with "is not a chat command".
21
+ *
22
+ * Shape settles most tokens, but a bare `/word` is ambiguous: `/tmp` and
23
+ * `/orchestration` are spelled the same. Only the filesystem separates them,
24
+ * so an unknown word is looked up — cheap, and reached only for a single
25
+ * bare word that no command matches.
26
+ */
27
+ export function classifySlashToken(token) {
28
+ // A separator beyond the leading one is unambiguously a path.
29
+ if (token.slice(1).includes("/") === true) {
30
+ return "path";
31
+ }
32
+ // Still a command while it prefixes one (`/he` → `/help`).
33
+ if (ASK_SLASH_COMMANDS.some((command) => command.startsWith(token)) === true) {
34
+ return "command";
35
+ }
36
+ // A bare word is only a path if one exists — `/deploy-prod` is a typo worth
37
+ // a card, not prose to spend a turn on.
38
+ return existsSync(token) === true ? "path" : "unknown";
39
+ }
40
+ /** Max rows shown in the live Ask suggestion menu. */
41
+ export const ASK_COMPLETION_MENU_LIMIT = 8;
42
+ /**
43
+ * Tab completer for the Ask prompt: slash commands when the line is `/…`,
44
+ * otherwise local path completion for a path-like last token.
45
+ */
46
+ export function createAskCompleter(cwd) {
47
+ return (line) => {
48
+ const slash = completeSlash(line);
49
+ if (slash !== undefined) {
50
+ return slash;
51
+ }
52
+ return completePath(line, cwd);
53
+ };
54
+ }
55
+ /**
56
+ * Matches for the live suggestion menu (slash commands, else path hits).
57
+ * Empty when the line is plain prose.
58
+ */
59
+ export function listCompletions(line, cwd) {
60
+ const slash = completeSlash(line);
61
+ if (slash !== undefined) {
62
+ return slash[0];
63
+ }
64
+ const [hits] = completePath(line, cwd);
65
+ return hits;
66
+ }
67
+ /**
68
+ * Replace the completed span with `completion`: whole line for slash
69
+ * commands, last token for paths.
70
+ */
71
+ export function applyCompletion(line, completion) {
72
+ if (completeSlash(line) !== undefined) {
73
+ return completion;
74
+ }
75
+ const token = lastToken(line);
76
+ if (token === undefined) {
77
+ return completion;
78
+ }
79
+ return `${line.slice(0, line.length - token.length)}${completion}`;
80
+ }
81
+ /**
82
+ * `/hel` → `/help`. Only when the whole line is a single `/token` (no spaces)
83
+ * and it matches a chat command prefix. `/tmp` falls through to path completion.
84
+ */
85
+ export function completeSlash(line) {
86
+ if (/^\/[A-Za-z]*$/.test(line) === false) {
87
+ return undefined;
88
+ }
89
+ const hits = ASK_SLASH_COMMANDS.filter((command) => command.startsWith(line));
90
+ if (hits.length === 0) {
91
+ return undefined;
92
+ }
93
+ return [[...hits], line];
94
+ }
95
+ /**
96
+ * Complete the last whitespace-separated token when it looks like a path.
97
+ * Exported for tests.
98
+ */
99
+ export function completePath(line, cwd) {
100
+ const token = lastToken(line);
101
+ if (token === undefined || isPathLike(token) === false) {
102
+ return [[], line];
103
+ }
104
+ const expanded = expandHome(token);
105
+ const absoluteBase = isAbsolute(expanded) ? expanded : resolve(cwd, expanded);
106
+ let dir;
107
+ let namePrefix;
108
+ // `.`, `..` and `~` name a directory to look inside, the same as a trailing
109
+ // separator. Splitting them with dirname/basename instead would complete
110
+ // siblings of the current, parent or home directory.
111
+ if (token.endsWith("/") ||
112
+ token.endsWith(sep) ||
113
+ token === "." ||
114
+ token === ".." ||
115
+ token === "~") {
116
+ dir = absoluteBase;
117
+ namePrefix = "";
118
+ }
119
+ else {
120
+ dir = dirname(absoluteBase);
121
+ namePrefix = basename(absoluteBase);
122
+ }
123
+ let entries;
124
+ try {
125
+ entries = readdirSync(dir);
126
+ }
127
+ catch {
128
+ return [[], token];
129
+ }
130
+ const hits = [];
131
+ for (const entry of entries) {
132
+ if (entry.startsWith(namePrefix) === false) {
133
+ continue;
134
+ }
135
+ // Hide dotfiles until the name being typed starts with a dot. Listing a
136
+ // directory (`.`, `~`, a trailing slash) has an empty prefix, so those
137
+ // stay hidden there too.
138
+ if (entry.startsWith(".") === true &&
139
+ namePrefix.startsWith(".") === false) {
140
+ continue;
141
+ }
142
+ const absoluteHit = join(dir, entry);
143
+ let display = collapseHome(toDisplayPath(absoluteHit, cwd, token));
144
+ try {
145
+ if (statSync(absoluteHit).isDirectory() === true) {
146
+ display = display.endsWith("/") ? display : `${display}/`;
147
+ }
148
+ }
149
+ catch {
150
+ // Skip unreadable entries.
151
+ continue;
152
+ }
153
+ hits.push(display);
154
+ }
155
+ hits.sort((a, b) => a.localeCompare(b));
156
+ return [hits, token];
157
+ }
158
+ function lastToken(line) {
159
+ const match = /(?:^|\s)(\S+)$/.exec(line);
160
+ if (match === null) {
161
+ return undefined;
162
+ }
163
+ return match[1];
164
+ }
165
+ function isPathLike(token) {
166
+ if (token.startsWith("./") === true ||
167
+ token.startsWith("../") === true ||
168
+ token.startsWith("~/") === true ||
169
+ token === "~" ||
170
+ token === "." ||
171
+ token === "..") {
172
+ return true;
173
+ }
174
+ if (token.startsWith("/") === true) {
175
+ return classifySlashToken(token) !== "command";
176
+ }
177
+ return token.includes("/");
178
+ }
179
+ function expandHome(token) {
180
+ if (token === "~") {
181
+ return homedir();
182
+ }
183
+ if (token.startsWith("~/") === true) {
184
+ return join(homedir(), token.slice(2));
185
+ }
186
+ return token;
187
+ }
188
+ function collapseHome(path) {
189
+ const home = homedir();
190
+ if (path === home) {
191
+ return "~";
192
+ }
193
+ if (path.startsWith(`${home}/`) === true) {
194
+ return `~/${path.slice(home.length + 1)}`;
195
+ }
196
+ return path;
197
+ }
198
+ /**
199
+ * Prefer a relative form when the typed token was relative; keep absolute /
200
+ * home forms when the user started that way.
201
+ */
202
+ function toDisplayPath(absolute, cwd, typedToken) {
203
+ if (typedToken.startsWith("~/") === true || typedToken === "~") {
204
+ return absolute;
205
+ }
206
+ if (isAbsolute(typedToken) === true) {
207
+ return absolute;
208
+ }
209
+ const rel = relative(cwd, absolute);
210
+ const display = rel === "" ? "." : rel;
211
+ if (typedToken.startsWith("./") === true &&
212
+ display.startsWith(".") === false &&
213
+ isAbsolute(display) === false) {
214
+ return `./${display}`;
215
+ }
216
+ return display;
217
+ }
@@ -0,0 +1,32 @@
1
+ import type { Api } from "../../api.js";
2
+ /**
3
+ * The earlier turns of a chat, rendered for a Claude Code session that has
4
+ * never seen them.
5
+ *
6
+ * Local Ask starts a fresh harness session on every CLI run — the harness can
7
+ * only resume Claude Code by rerunning it, and a rerun cancels whatever tool
8
+ * approval is in flight, which Claude Code then records as the user refusing
9
+ * the tool. Continuity therefore comes from the chat history the API already
10
+ * has, replayed into the first prompt.
11
+ *
12
+ * Best effort: a chat whose history cannot be read is answered without it
13
+ * rather than not at all.
14
+ */
15
+ export declare function loadChatHistoryPreamble(input: {
16
+ api: Api;
17
+ chatUuid: string;
18
+ }): Promise<string | undefined>;
19
+ type HistoryMessage = {
20
+ type: string;
21
+ status?: string;
22
+ parts: Array<{
23
+ type: string;
24
+ }>;
25
+ };
26
+ /**
27
+ * Exported for tests. Takes the API's messages oldest-first and returns the
28
+ * preamble, or `undefined` when there is nothing worth replaying.
29
+ */
30
+ export declare function renderHistoryPreamble(messages: readonly HistoryMessage[]): string | undefined;
31
+ export {};
32
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AA0BxC;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACnD,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU9B;AAED,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAClC,MAAM,GAAG,SAAS,CA6CpB"}