@gmickel/gno 0.25.2 → 0.27.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/README.md +5 -3
- package/assets/skill/SKILL.md +5 -0
- package/assets/skill/cli-reference.md +8 -6
- package/package.json +1 -1
- package/src/cli/commands/get.ts +21 -0
- package/src/cli/commands/skill/install.ts +2 -2
- package/src/cli/commands/skill/paths.ts +26 -4
- package/src/cli/commands/skill/uninstall.ts +2 -2
- package/src/cli/program.ts +18 -12
- package/src/core/document-capabilities.ts +113 -0
- package/src/mcp/tools/get.ts +10 -0
- package/src/mcp/tools/index.ts +434 -110
- package/src/sdk/documents.ts +12 -0
- package/src/serve/doc-events.ts +69 -0
- package/src/serve/public/app.tsx +81 -24
- package/src/serve/public/components/CaptureModal.tsx +138 -3
- package/src/serve/public/components/QuickSwitcher.tsx +248 -0
- package/src/serve/public/components/ShortcutHelpModal.tsx +1 -0
- package/src/serve/public/components/ai-elements/code-block.tsx +74 -26
- package/src/serve/public/components/editor/CodeMirrorEditor.tsx +51 -0
- package/src/serve/public/components/ui/command.tsx +2 -2
- package/src/serve/public/hooks/use-doc-events.ts +34 -0
- package/src/serve/public/hooks/useCaptureModal.tsx +12 -3
- package/src/serve/public/hooks/useKeyboardShortcuts.ts +2 -2
- package/src/serve/public/lib/deep-links.ts +68 -0
- package/src/serve/public/lib/document-availability.ts +22 -0
- package/src/serve/public/lib/local-history.ts +44 -0
- package/src/serve/public/lib/wiki-link.ts +36 -0
- package/src/serve/public/pages/Browse.tsx +11 -0
- package/src/serve/public/pages/Dashboard.tsx +2 -2
- package/src/serve/public/pages/DocView.tsx +241 -18
- package/src/serve/public/pages/DocumentEditor.tsx +399 -9
- package/src/serve/public/pages/Search.tsx +20 -1
- package/src/serve/routes/api.ts +359 -28
- package/src/serve/server.ts +48 -1
- package/src/serve/watch-service.ts +149 -0
package/README.md
CHANGED
|
@@ -194,9 +194,11 @@ Check status: `gno mcp status`
|
|
|
194
194
|
Skills integrate via CLI with no MCP overhead:
|
|
195
195
|
|
|
196
196
|
```bash
|
|
197
|
-
gno skill install --scope user
|
|
198
|
-
gno skill install --target codex
|
|
199
|
-
gno skill install --target
|
|
197
|
+
gno skill install --scope user # User-wide
|
|
198
|
+
gno skill install --target codex # Codex
|
|
199
|
+
gno skill install --target opencode # OpenCode
|
|
200
|
+
gno skill install --target openclaw # OpenClaw
|
|
201
|
+
gno skill install --target all # All targets
|
|
200
202
|
```
|
|
201
203
|
|
|
202
204
|
> **Full setup guide**: [MCP Integration](https://gno.sh/docs/MCP/) · [CLI Reference](https://gno.sh/docs/CLI/)
|
package/assets/skill/SKILL.md
CHANGED
|
@@ -110,10 +110,15 @@ gno get gno://work/report.md --from 100 -l 20
|
|
|
110
110
|
# With line numbers
|
|
111
111
|
gno get gno://work/report.md --line-numbers
|
|
112
112
|
|
|
113
|
+
# JSON output with capabilities metadata
|
|
114
|
+
gno get gno://work/report.md --json
|
|
115
|
+
|
|
113
116
|
# Multiple documents
|
|
114
117
|
gno multi-get gno://work/doc1.md gno://work/doc2.md
|
|
115
118
|
```
|
|
116
119
|
|
|
120
|
+
**Editable vs read-only**: `gno get --json` returns a `capabilities` field showing whether a document is editable at its source. Markdown and plain text files are editable in place. Converted documents (PDF, DOCX, XLSX) are read-only -- to edit their content, create a new markdown note instead of overwriting the binary source.
|
|
121
|
+
|
|
117
122
|
## Search Then Get (common pipeline)
|
|
118
123
|
|
|
119
124
|
```bash
|
|
@@ -488,18 +488,20 @@ Install GNO skill for AI coding assistants.
|
|
|
488
488
|
gno skill install [options]
|
|
489
489
|
```
|
|
490
490
|
|
|
491
|
-
| Option | Default | Description
|
|
492
|
-
| -------------- | ------- |
|
|
493
|
-
| `-t, --target` | claude | Target: `claude`, `codex`, `
|
|
494
|
-
| `-s, --scope` | user | Scope: `user`, `project`
|
|
495
|
-
| `-f, --force` | false | Overwrite existing
|
|
496
|
-
| `--dry-run` | false | Preview changes
|
|
491
|
+
| Option | Default | Description |
|
|
492
|
+
| -------------- | ------- | -------------------------------------------------------- |
|
|
493
|
+
| `-t, --target` | claude | Target: `claude`, `codex`, `opencode`, `openclaw`, `all` |
|
|
494
|
+
| `-s, --scope` | user | Scope: `user`, `project` |
|
|
495
|
+
| `-f, --force` | false | Overwrite existing |
|
|
496
|
+
| `--dry-run` | false | Preview changes |
|
|
497
497
|
|
|
498
498
|
Examples:
|
|
499
499
|
|
|
500
500
|
```bash
|
|
501
501
|
gno skill install --target claude --scope project
|
|
502
502
|
gno skill install --target codex --scope user
|
|
503
|
+
gno skill install --target openclaw --scope user
|
|
504
|
+
gno skill install --target all --force # Install to all targets
|
|
503
505
|
```
|
|
504
506
|
|
|
505
507
|
### gno skill uninstall
|
package/package.json
CHANGED
package/src/cli/commands/get.ts
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
import type { DocumentRow, StorePort, StoreResult } from "../../store/types";
|
|
9
9
|
import type { ParsedRef } from "./ref-parser";
|
|
10
10
|
|
|
11
|
+
import {
|
|
12
|
+
getDocumentCapabilities,
|
|
13
|
+
type DocumentCapabilities,
|
|
14
|
+
} from "../../core/document-capabilities";
|
|
11
15
|
import { parseRef } from "./ref-parser";
|
|
12
16
|
import { initStore } from "./shared";
|
|
13
17
|
|
|
@@ -58,6 +62,7 @@ export interface GetResponse {
|
|
|
58
62
|
converterVersion?: string;
|
|
59
63
|
mirrorHash?: string;
|
|
60
64
|
};
|
|
65
|
+
capabilities: DocumentCapabilities;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -203,6 +208,7 @@ function buildResponse(ctx: BuildResponseContext): GetResult {
|
|
|
203
208
|
language: doc.languageHint ?? undefined,
|
|
204
209
|
source: buildSourceMeta(doc, config),
|
|
205
210
|
conversion: buildConversionMeta(doc),
|
|
211
|
+
capabilities: buildCapabilities(doc),
|
|
206
212
|
},
|
|
207
213
|
};
|
|
208
214
|
}
|
|
@@ -233,6 +239,7 @@ function buildResponse(ctx: BuildResponseContext): GetResult {
|
|
|
233
239
|
language: doc.languageHint ?? undefined,
|
|
234
240
|
source: buildSourceMeta(doc, config),
|
|
235
241
|
conversion: buildConversionMeta(doc),
|
|
242
|
+
capabilities: buildCapabilities(doc),
|
|
236
243
|
},
|
|
237
244
|
};
|
|
238
245
|
}
|
|
@@ -247,6 +254,7 @@ interface DocRow {
|
|
|
247
254
|
sourceMime: string;
|
|
248
255
|
sourceExt: string;
|
|
249
256
|
sourceSize: number;
|
|
257
|
+
sourceMtime?: string;
|
|
250
258
|
sourceHash: string;
|
|
251
259
|
}
|
|
252
260
|
|
|
@@ -262,11 +270,24 @@ function buildSourceMeta(
|
|
|
262
270
|
relPath: doc.relPath,
|
|
263
271
|
mime: doc.sourceMime,
|
|
264
272
|
ext: doc.sourceExt,
|
|
273
|
+
modifiedAt: doc.sourceMtime ?? undefined,
|
|
265
274
|
sizeBytes: doc.sourceSize,
|
|
266
275
|
sourceHash: doc.sourceHash,
|
|
267
276
|
};
|
|
268
277
|
}
|
|
269
278
|
|
|
279
|
+
function buildCapabilities(doc: {
|
|
280
|
+
sourceExt: string;
|
|
281
|
+
sourceMime: string;
|
|
282
|
+
mirrorHash?: string | null;
|
|
283
|
+
}): DocumentCapabilities {
|
|
284
|
+
return getDocumentCapabilities({
|
|
285
|
+
sourceExt: doc.sourceExt,
|
|
286
|
+
sourceMime: doc.sourceMime,
|
|
287
|
+
contentAvailable: doc.mirrorHash !== null,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
270
291
|
interface ConversionDoc {
|
|
271
292
|
converterId?: string | null;
|
|
272
293
|
converterVersion?: string | null;
|
|
@@ -13,6 +13,7 @@ import { CliError } from "../../errors.js";
|
|
|
13
13
|
import { getGlobals } from "../../program.js";
|
|
14
14
|
import {
|
|
15
15
|
resolveSkillPaths,
|
|
16
|
+
SKILL_TARGETS,
|
|
16
17
|
type SkillScope,
|
|
17
18
|
type SkillTarget,
|
|
18
19
|
validatePathForDeletion,
|
|
@@ -171,8 +172,7 @@ export async function installSkill(opts: InstallOptions = {}): Promise<void> {
|
|
|
171
172
|
const yes = opts.yes ?? globals.yes;
|
|
172
173
|
const quiet = opts.quiet ?? globals.quiet;
|
|
173
174
|
|
|
174
|
-
const targets: SkillTarget[] =
|
|
175
|
-
target === "all" ? ["claude", "codex"] : [target];
|
|
175
|
+
const targets: SkillTarget[] = target === "all" ? SKILL_TARGETS : [target];
|
|
176
176
|
|
|
177
177
|
const results: InstallResult[] = [];
|
|
178
178
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Path resolution for skill installation.
|
|
3
|
-
* Supports Claude Code and
|
|
4
|
-
* Note: OpenCode and Amp use the same .claude path as Claude Code.
|
|
3
|
+
* Supports Claude Code, Codex, OpenCode, and OpenClaw targets with project/user scopes.
|
|
5
4
|
*
|
|
6
5
|
* @module src/cli/commands/skill/paths
|
|
7
6
|
*/
|
|
@@ -22,14 +21,25 @@ export const ENV_CLAUDE_SKILLS_DIR = "CLAUDE_SKILLS_DIR";
|
|
|
22
21
|
/** Override Codex skills directory */
|
|
23
22
|
export const ENV_CODEX_SKILLS_DIR = "CODEX_SKILLS_DIR";
|
|
24
23
|
|
|
24
|
+
/** Override OpenCode skills directory */
|
|
25
|
+
export const ENV_OPENCODE_SKILLS_DIR = "OPENCODE_SKILLS_DIR";
|
|
26
|
+
|
|
27
|
+
/** Override OpenClaw skills directory */
|
|
28
|
+
export const ENV_OPENCLAW_SKILLS_DIR = "OPENCLAW_SKILLS_DIR";
|
|
29
|
+
|
|
25
30
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
31
|
// Types
|
|
27
32
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
33
|
|
|
29
34
|
export type SkillScope = "project" | "user";
|
|
30
|
-
export type SkillTarget = "claude" | "codex";
|
|
35
|
+
export type SkillTarget = "claude" | "codex" | "opencode" | "openclaw";
|
|
31
36
|
|
|
32
|
-
export const SKILL_TARGETS: SkillTarget[] = [
|
|
37
|
+
export const SKILL_TARGETS: SkillTarget[] = [
|
|
38
|
+
"claude",
|
|
39
|
+
"codex",
|
|
40
|
+
"opencode",
|
|
41
|
+
"openclaw",
|
|
42
|
+
];
|
|
33
43
|
|
|
34
44
|
export interface SkillPathOptions {
|
|
35
45
|
scope: SkillScope;
|
|
@@ -77,6 +87,18 @@ const TARGET_CONFIGS: Record<SkillTarget, TargetPathConfig> = {
|
|
|
77
87
|
skillsSubdir: "skills",
|
|
78
88
|
envVar: ENV_CODEX_SKILLS_DIR,
|
|
79
89
|
},
|
|
90
|
+
opencode: {
|
|
91
|
+
projectBase: ".opencode",
|
|
92
|
+
userBase: ".config/opencode",
|
|
93
|
+
skillsSubdir: "skills",
|
|
94
|
+
envVar: ENV_OPENCODE_SKILLS_DIR,
|
|
95
|
+
},
|
|
96
|
+
openclaw: {
|
|
97
|
+
projectBase: ".openclaw",
|
|
98
|
+
userBase: ".openclaw",
|
|
99
|
+
skillsSubdir: "skills",
|
|
100
|
+
envVar: ENV_OPENCLAW_SKILLS_DIR,
|
|
101
|
+
},
|
|
80
102
|
};
|
|
81
103
|
|
|
82
104
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -12,6 +12,7 @@ import { CliError } from "../../errors.js";
|
|
|
12
12
|
import { getGlobals } from "../../program.js";
|
|
13
13
|
import {
|
|
14
14
|
resolveSkillPaths,
|
|
15
|
+
SKILL_TARGETS,
|
|
15
16
|
type SkillScope,
|
|
16
17
|
type SkillTarget,
|
|
17
18
|
validatePathForDeletion,
|
|
@@ -100,8 +101,7 @@ export async function uninstallSkill(
|
|
|
100
101
|
const json = opts.json ?? globals.json;
|
|
101
102
|
const quiet = opts.quiet ?? globals.quiet;
|
|
102
103
|
|
|
103
|
-
const targets: SkillTarget[] =
|
|
104
|
-
target === "all" ? ["claude", "codex"] : [target];
|
|
104
|
+
const targets: SkillTarget[] = target === "all" ? SKILL_TARGETS : [target];
|
|
105
105
|
|
|
106
106
|
const results: UninstallResult[] = [];
|
|
107
107
|
const notFound: string[] = [];
|
package/src/cli/program.ts
CHANGED
|
@@ -1578,7 +1578,7 @@ function wireSkillCommands(program: Command): void {
|
|
|
1578
1578
|
)
|
|
1579
1579
|
.option(
|
|
1580
1580
|
"-t, --target <target>",
|
|
1581
|
-
"target agent (claude, codex, all)",
|
|
1581
|
+
"target agent (claude, codex, opencode, openclaw, all)",
|
|
1582
1582
|
"claude"
|
|
1583
1583
|
)
|
|
1584
1584
|
.option("-f, --force", "overwrite existing installation")
|
|
@@ -1595,17 +1595,19 @@ function wireSkillCommands(program: Command): void {
|
|
|
1595
1595
|
);
|
|
1596
1596
|
}
|
|
1597
1597
|
// Validate target
|
|
1598
|
-
if (
|
|
1598
|
+
if (
|
|
1599
|
+
!["claude", "codex", "opencode", "openclaw", "all"].includes(target)
|
|
1600
|
+
) {
|
|
1599
1601
|
throw new CliError(
|
|
1600
1602
|
"VALIDATION",
|
|
1601
|
-
`Invalid target: ${target}. Must be 'claude', 'codex', or 'all'.`
|
|
1603
|
+
`Invalid target: ${target}. Must be 'claude', 'codex', 'opencode', 'openclaw', or 'all'.`
|
|
1602
1604
|
);
|
|
1603
1605
|
}
|
|
1604
1606
|
|
|
1605
1607
|
const { installSkill } = await import("./commands/skill/install.js");
|
|
1606
1608
|
await installSkill({
|
|
1607
1609
|
scope: scope as "project" | "user",
|
|
1608
|
-
target: target as "claude" | "codex" | "all",
|
|
1610
|
+
target: target as "claude" | "codex" | "opencode" | "openclaw" | "all",
|
|
1609
1611
|
force: Boolean(cmdOpts.force),
|
|
1610
1612
|
json: Boolean(cmdOpts.json),
|
|
1611
1613
|
});
|
|
@@ -1621,7 +1623,7 @@ function wireSkillCommands(program: Command): void {
|
|
|
1621
1623
|
)
|
|
1622
1624
|
.option(
|
|
1623
1625
|
"-t, --target <target>",
|
|
1624
|
-
"target agent (claude, codex, all)",
|
|
1626
|
+
"target agent (claude, codex, opencode, openclaw, all)",
|
|
1625
1627
|
"claude"
|
|
1626
1628
|
)
|
|
1627
1629
|
.option("--json", "JSON output")
|
|
@@ -1637,17 +1639,19 @@ function wireSkillCommands(program: Command): void {
|
|
|
1637
1639
|
);
|
|
1638
1640
|
}
|
|
1639
1641
|
// Validate target
|
|
1640
|
-
if (
|
|
1642
|
+
if (
|
|
1643
|
+
!["claude", "codex", "opencode", "openclaw", "all"].includes(target)
|
|
1644
|
+
) {
|
|
1641
1645
|
throw new CliError(
|
|
1642
1646
|
"VALIDATION",
|
|
1643
|
-
`Invalid target: ${target}. Must be 'claude', 'codex', or 'all'.`
|
|
1647
|
+
`Invalid target: ${target}. Must be 'claude', 'codex', 'opencode', 'openclaw', or 'all'.`
|
|
1644
1648
|
);
|
|
1645
1649
|
}
|
|
1646
1650
|
|
|
1647
1651
|
const { uninstallSkill } = await import("./commands/skill/uninstall.js");
|
|
1648
1652
|
await uninstallSkill({
|
|
1649
1653
|
scope: scope as "project" | "user",
|
|
1650
|
-
target: target as "claude" | "codex" | "all",
|
|
1654
|
+
target: target as "claude" | "codex" | "opencode" | "openclaw" | "all",
|
|
1651
1655
|
json: Boolean(cmdOpts.json),
|
|
1652
1656
|
});
|
|
1653
1657
|
});
|
|
@@ -1675,7 +1679,7 @@ function wireSkillCommands(program: Command): void {
|
|
|
1675
1679
|
)
|
|
1676
1680
|
.option(
|
|
1677
1681
|
"-t, --target <target>",
|
|
1678
|
-
"filter by target (claude, codex, all)",
|
|
1682
|
+
"filter by target (claude, codex, opencode, openclaw, all)",
|
|
1679
1683
|
"all"
|
|
1680
1684
|
)
|
|
1681
1685
|
.option("--json", "JSON output")
|
|
@@ -1691,17 +1695,19 @@ function wireSkillCommands(program: Command): void {
|
|
|
1691
1695
|
);
|
|
1692
1696
|
}
|
|
1693
1697
|
// Validate target
|
|
1694
|
-
if (
|
|
1698
|
+
if (
|
|
1699
|
+
!["claude", "codex", "opencode", "openclaw", "all"].includes(target)
|
|
1700
|
+
) {
|
|
1695
1701
|
throw new CliError(
|
|
1696
1702
|
"VALIDATION",
|
|
1697
|
-
`Invalid target: ${target}. Must be 'claude', 'codex', or 'all'.`
|
|
1703
|
+
`Invalid target: ${target}. Must be 'claude', 'codex', 'opencode', 'openclaw', or 'all'.`
|
|
1698
1704
|
);
|
|
1699
1705
|
}
|
|
1700
1706
|
|
|
1701
1707
|
const { showPaths } = await import("./commands/skill/paths-cmd.js");
|
|
1702
1708
|
await showPaths({
|
|
1703
1709
|
scope: scope as "project" | "user" | "all",
|
|
1704
|
-
target: target as "claude" | "codex" | "all",
|
|
1710
|
+
target: target as "claude" | "codex" | "opencode" | "openclaw" | "all",
|
|
1705
1711
|
json: Boolean(cmdOpts.json),
|
|
1706
1712
|
});
|
|
1707
1713
|
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// node:path has no Bun equivalent
|
|
2
|
+
import { posix as pathPosix } from "node:path";
|
|
3
|
+
|
|
4
|
+
export type DocumentCapabilityMode = "editable" | "read_only";
|
|
5
|
+
|
|
6
|
+
export interface DocumentCapabilities {
|
|
7
|
+
editable: boolean;
|
|
8
|
+
tagsEditable: boolean;
|
|
9
|
+
tagsWriteback: boolean;
|
|
10
|
+
canCreateEditableCopy: boolean;
|
|
11
|
+
mode: DocumentCapabilityMode;
|
|
12
|
+
reason?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const EDITABLE_EXTENSIONS = new Set([
|
|
16
|
+
".md",
|
|
17
|
+
".markdown",
|
|
18
|
+
".mdx",
|
|
19
|
+
".txt",
|
|
20
|
+
".text",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function isTextLikeMime(mime: string): boolean {
|
|
24
|
+
return mime.startsWith("text/");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getDocumentCapabilities(input: {
|
|
28
|
+
sourceExt: string;
|
|
29
|
+
sourceMime: string;
|
|
30
|
+
contentAvailable: boolean;
|
|
31
|
+
}): DocumentCapabilities {
|
|
32
|
+
const ext = input.sourceExt.toLowerCase();
|
|
33
|
+
const editable =
|
|
34
|
+
EDITABLE_EXTENSIONS.has(ext) || isTextLikeMime(input.sourceMime);
|
|
35
|
+
const tagsWriteback = ext === ".md" || ext === ".markdown" || ext === ".mdx";
|
|
36
|
+
|
|
37
|
+
if (editable) {
|
|
38
|
+
return {
|
|
39
|
+
editable: true,
|
|
40
|
+
tagsEditable: true,
|
|
41
|
+
tagsWriteback,
|
|
42
|
+
canCreateEditableCopy: false,
|
|
43
|
+
mode: "editable",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
editable: false,
|
|
49
|
+
tagsEditable: true,
|
|
50
|
+
tagsWriteback: false,
|
|
51
|
+
canCreateEditableCopy: input.contentAvailable,
|
|
52
|
+
mode: "read_only",
|
|
53
|
+
reason:
|
|
54
|
+
"This document is derived from a source format that GNO cannot safely write back in place.",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function deriveEditableCopyRelPath(
|
|
59
|
+
relPath: string,
|
|
60
|
+
existingRelPaths: Iterable<string> = []
|
|
61
|
+
): string {
|
|
62
|
+
const parsed = pathPosix.parse(relPath);
|
|
63
|
+
const prefix = parsed.dir ? `${parsed.dir}/` : "";
|
|
64
|
+
const baseName = parsed.name || "copy";
|
|
65
|
+
const existing = new Set(existingRelPaths);
|
|
66
|
+
|
|
67
|
+
const baseCandidate =
|
|
68
|
+
parsed.ext.toLowerCase() === ".md" ||
|
|
69
|
+
parsed.ext.toLowerCase() === ".markdown" ||
|
|
70
|
+
parsed.ext.toLowerCase() === ".mdx"
|
|
71
|
+
? `${prefix}${baseName}.copy.md`
|
|
72
|
+
: `${prefix}${baseName}.md`;
|
|
73
|
+
|
|
74
|
+
if (!existing.has(baseCandidate)) {
|
|
75
|
+
return baseCandidate;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let counter = 2;
|
|
79
|
+
while (true) {
|
|
80
|
+
const candidate = `${prefix}${baseName}.copy-${counter}.md`;
|
|
81
|
+
if (!existing.has(candidate)) {
|
|
82
|
+
return candidate;
|
|
83
|
+
}
|
|
84
|
+
counter += 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function buildEditableCopyContent(input: {
|
|
89
|
+
title: string;
|
|
90
|
+
sourceDocid: string;
|
|
91
|
+
sourceUri: string;
|
|
92
|
+
sourceMime: string;
|
|
93
|
+
sourceExt: string;
|
|
94
|
+
content: string;
|
|
95
|
+
tags?: string[];
|
|
96
|
+
}): string {
|
|
97
|
+
const frontmatterLines = [
|
|
98
|
+
`title: ${JSON.stringify(input.title)}`,
|
|
99
|
+
`gno_source_docid: ${JSON.stringify(input.sourceDocid)}`,
|
|
100
|
+
`gno_source_uri: ${JSON.stringify(input.sourceUri)}`,
|
|
101
|
+
`gno_source_mime: ${JSON.stringify(input.sourceMime)}`,
|
|
102
|
+
`gno_source_ext: ${JSON.stringify(input.sourceExt)}`,
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
if (input.tags && input.tags.length > 0) {
|
|
106
|
+
frontmatterLines.push("tags:");
|
|
107
|
+
for (const tag of input.tags) {
|
|
108
|
+
frontmatterLines.push(` - ${JSON.stringify(tag)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return `---\n${frontmatterLines.join("\n")}\n---\n\n${input.content}`;
|
|
113
|
+
}
|
package/src/mcp/tools/get.ts
CHANGED
|
@@ -11,6 +11,10 @@ import type { ToolContext } from "../server";
|
|
|
11
11
|
|
|
12
12
|
import { parseUri } from "../../app/constants";
|
|
13
13
|
import { parseRef } from "../../cli/commands/ref-parser";
|
|
14
|
+
import {
|
|
15
|
+
getDocumentCapabilities,
|
|
16
|
+
type DocumentCapabilities,
|
|
17
|
+
} from "../../core/document-capabilities";
|
|
14
18
|
import { runTool, type ToolResult } from "./index";
|
|
15
19
|
|
|
16
20
|
interface GetInput {
|
|
@@ -42,6 +46,7 @@ interface GetResponse {
|
|
|
42
46
|
converterVersion?: string;
|
|
43
47
|
mirrorHash?: string;
|
|
44
48
|
};
|
|
49
|
+
capabilities: DocumentCapabilities;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
/**
|
|
@@ -213,6 +218,11 @@ export function handleGet(
|
|
|
213
218
|
mirrorHash: doc.mirrorHash,
|
|
214
219
|
}
|
|
215
220
|
: undefined,
|
|
221
|
+
capabilities: getDocumentCapabilities({
|
|
222
|
+
sourceExt: doc.sourceExt,
|
|
223
|
+
sourceMime: doc.sourceMime,
|
|
224
|
+
contentAvailable: doc.mirrorHash !== null,
|
|
225
|
+
}),
|
|
216
226
|
};
|
|
217
227
|
|
|
218
228
|
return response;
|