codex-arsenal 0.2.1 → 0.4.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/CODEX.md +33 -3
- package/README.md +58 -6
- package/bin/cli.js +8 -5
- package/bin/obsidian-bridge.js +120 -0
- package/configs/codex/session-config.json +30 -0
- package/docs/codex-plugin-distribution.md +54 -0
- package/docs/npm-publishing.md +35 -0
- package/lib/installer.js +28 -5
- package/lib/manifest.js +47 -7
- package/lib/obsidian-bridge.js +354 -0
- package/package.json +6 -5
- package/plugins/obsidian-codex-bridge/README.md +30 -0
- package/plugins/obsidian-codex-bridge/plugin.json +6 -0
- package/prompts/code-style.md +21 -0
- package/prompts/meaningful-work.md +30 -0
- package/prompts/session-bootstrap.md +22 -0
- package/skills/obsidian-session-loop/SKILL.md +50 -0
- package/CLAUDE.md +0 -15
package/CODEX.md
CHANGED
|
@@ -6,6 +6,23 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## Session Bootstrap
|
|
10
|
+
|
|
11
|
+
When this repository is installed into a project, read `prompts/session-bootstrap.md` immediately after `CODEX.md` if it exists.
|
|
12
|
+
|
|
13
|
+
If `configs/codex/session-config.json` exists, use it as the project-local source of truth for:
|
|
14
|
+
|
|
15
|
+
- the session initializer path;
|
|
16
|
+
- the code-style note path;
|
|
17
|
+
- the meaningful-work criteria;
|
|
18
|
+
- the Obsidian vault and inbox paths.
|
|
19
|
+
|
|
20
|
+
If the configured code-style note exists, merge it into your working style before editing files.
|
|
21
|
+
|
|
22
|
+
If the task satisfies the meaningful-work criteria, draft an Obsidian article at the end of the session.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
9
26
|
## 1. Think Before Coding
|
|
10
27
|
|
|
11
28
|
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
|
@@ -91,7 +108,20 @@ Weak criteria ("make it work") require constant clarification.
|
|
|
91
108
|
|
|
92
109
|
---
|
|
93
110
|
|
|
94
|
-
## 6.
|
|
111
|
+
## 6. Token Budgeting
|
|
112
|
+
|
|
113
|
+
**Spend context only where it changes the decision.**
|
|
114
|
+
|
|
115
|
+
- Start with filenames, symbols, and short snippets before reading full files.
|
|
116
|
+
- Prefer `rg` and targeted search over broad recursive reads.
|
|
117
|
+
- Summarize long outputs immediately: key facts, file paths, line numbers, open questions.
|
|
118
|
+
- Do not paste large logs back to the user. Quote only the failing line or smallest useful excerpt.
|
|
119
|
+
- Reuse already-loaded context instead of re-reading the same files.
|
|
120
|
+
- When handing off, compress to: goal, changed files, commands run, verification status, next risk.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 7. Context Awareness
|
|
95
125
|
|
|
96
126
|
**Codex has a limited context window — use it wisely.**
|
|
97
127
|
|
|
@@ -102,7 +132,7 @@ Weak criteria ("make it work") require constant clarification.
|
|
|
102
132
|
|
|
103
133
|
---
|
|
104
134
|
|
|
105
|
-
##
|
|
135
|
+
## 8. Tool Use Discipline
|
|
106
136
|
|
|
107
137
|
**Every tool call has a cost. Make them count.**
|
|
108
138
|
|
|
@@ -118,4 +148,4 @@ Weak criteria ("make it work") require constant clarification.
|
|
|
118
148
|
- Diffs contain fewer unnecessary changes
|
|
119
149
|
- Fewer rewrites due to overcomplication
|
|
120
150
|
- Clarifying questions come **before** implementation, not after mistakes
|
|
121
|
-
- Agentic runs complete tasks end-to-end without silent failures or scope creep
|
|
151
|
+
- Agentic runs complete tasks end-to-end without silent failures or scope creep
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Codex-Arsenal
|
|
2
2
|
|
|
3
|
-
Practical building blocks for working with OpenAI Codex
|
|
3
|
+
Practical building blocks for working with OpenAI Codex.
|
|
4
4
|
|
|
5
|
-
Codex-Arsenal is not an awesome list. It is a small, installable collection of
|
|
5
|
+
Codex-Arsenal is not an awesome list. It is a small, installable collection of Codex guidelines, skills, prompts, workflows, configs, and plugin sketches that can be copied into real projects.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -39,14 +39,19 @@ npm exec --yes --package=codex-arsenal -- codex-arsenal list
|
|
|
39
39
|
## CLI
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
codex-arsenal init [--yes] [--dir <path>]
|
|
42
|
+
codex-arsenal init [--yes] [--force] [--dir <path>]
|
|
43
43
|
codex-arsenal list
|
|
44
|
-
codex-arsenal get <id...> [--dir <path>]
|
|
44
|
+
codex-arsenal get <id...> [--force] [--dir <path>]
|
|
45
|
+
codex-obsidian-bridge sync [--dir <path>] [--force]
|
|
46
|
+
codex-obsidian-bridge draft --title <text> --summary <text> [options]
|
|
45
47
|
```
|
|
46
48
|
|
|
47
49
|
- `init` opens a small selector. With `--yes`, it installs default items without prompting.
|
|
48
50
|
- `list` prints all installable manifest entries grouped by category.
|
|
49
51
|
- `get` installs one or more manifest entries by id.
|
|
52
|
+
- Existing files are skipped by default. Add `--force` to overwrite them intentionally.
|
|
53
|
+
- `codex-obsidian-bridge sync` mirrors the session bootstrap, code style, and meaningful-work notes into an Obsidian vault.
|
|
54
|
+
- `codex-obsidian-bridge draft` writes an article draft into the configured Obsidian inbox when the meaningful-work threshold is met.
|
|
50
55
|
|
|
51
56
|
The package is published on npm as [`codex-arsenal`](https://www.npmjs.com/package/codex-arsenal).
|
|
52
57
|
|
|
@@ -57,19 +62,22 @@ The package is published on npm as [`codex-arsenal`](https://www.npmjs.com/packa
|
|
|
57
62
|
| ID | Installs | Purpose |
|
|
58
63
|
| --- | --- | --- |
|
|
59
64
|
| `codex-md` | `CODEX.md` | Project-local behavioral guardrails for Codex-style agents. |
|
|
60
|
-
| `claude-md` | `CLAUDE.md` | Project-local behavioral guardrails for Claude Code. |
|
|
61
65
|
|
|
62
66
|
### Configs
|
|
63
67
|
|
|
64
68
|
| ID | Installs | Purpose |
|
|
65
69
|
| --- | --- | --- |
|
|
66
70
|
| `config-codex` | `.codex/config.json` | Starter Codex config. |
|
|
71
|
+
| `config-session-bootstrap` | `.codex/session-config.json` | Session bootstrap and Obsidian bridge settings. |
|
|
67
72
|
| `config-vscode` | `.vscode/settings.json` | VS Code settings for agent-assisted development. |
|
|
68
73
|
|
|
69
74
|
### Prompts
|
|
70
75
|
|
|
71
76
|
| ID | Installs | Purpose |
|
|
72
77
|
| --- | --- | --- |
|
|
78
|
+
| `prompt-session-bootstrap` | `prompts/session-bootstrap.md` | Session startup instructions for Codex. |
|
|
79
|
+
| `prompt-code-style` | `prompts/code-style.md` | Obsidian-backed code style note template. |
|
|
80
|
+
| `prompt-meaningful-work` | `prompts/meaningful-work.md` | Criteria and template for end-of-session articles. |
|
|
73
81
|
| `prompt-solo-dev` | `prompts/system-prompts/solo-dev.md` | Solo developer system prompt template. |
|
|
74
82
|
|
|
75
83
|
### Skills
|
|
@@ -79,12 +87,43 @@ The package is published on npm as [`codex-arsenal`](https://www.npmjs.com/packa
|
|
|
79
87
|
| `skill-debug-workflow` | `skills/debug-workflow/` | Reproduce, diagnose, test, and fix bugs systematically. |
|
|
80
88
|
| `skill-test-gen` | `skills/test-gen/` | Generate focused tests from behavior notes and function signatures. |
|
|
81
89
|
| `skill-publishing-npm-packages` | `skills/publishing-npm-packages/SKILL.md` | Prepare, troubleshoot, and automate npm releases with Trusted Publishing. |
|
|
90
|
+
| `skill-obsidian-session-loop` | `skills/obsidian-session-loop/SKILL.md` | Session startup, Obsidian sync, and end-of-session article drafting. |
|
|
82
91
|
|
|
83
92
|
### Plugins
|
|
84
93
|
|
|
85
94
|
| ID | Installs | Purpose |
|
|
86
95
|
| --- | --- | --- |
|
|
87
96
|
| `plugin-context-window-compressor` | `plugins/context-window-compressor/` | Plugin sketch for compressing long agent context into concise handoff notes. |
|
|
97
|
+
| `plugin-obsidian-codex-bridge` | `plugins/obsidian-codex-bridge/` | Plugin sketch for syncing Codex notes and article drafts with Obsidian. |
|
|
98
|
+
|
|
99
|
+
## Obsidian Bridge
|
|
100
|
+
|
|
101
|
+
The bridge is designed around two repeatable actions:
|
|
102
|
+
|
|
103
|
+
1. `sync` the session initializer and style notes into a vault so the notes stay close to where they are read.
|
|
104
|
+
2. `draft` a session article when the configured meaningful-work criteria are met.
|
|
105
|
+
|
|
106
|
+
The default config lives in `.codex/session-config.json` and can be customized per project. A minimal vault layout looks like this:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
Codex/
|
|
110
|
+
Session-Initializer.md
|
|
111
|
+
Code-Style.md
|
|
112
|
+
Meaningful-Work.md
|
|
113
|
+
Inbox/
|
|
114
|
+
Codex/
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Example commands:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
codex-obsidian-bridge sync --dir .
|
|
121
|
+
codex-obsidian-bridge open --dir . --file "Codex/Session-Initializer.md"
|
|
122
|
+
codex-obsidian-bridge reveal --dir . --file "Inbox/Codex/2026-06-11-refined-session-bootstrap.md"
|
|
123
|
+
codex-obsidian-bridge draft --dir . --title "Refined session bootstrap" --summary "Added an Obsidian bridge and session-level startup guidance." --changes "added bridge CLI, added note templates" --decisions "kept config file-based" --verification "npm test" --signals "new integration boundary, README update"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If you have an Obsidian CLI or local launcher, add `obsidian.launch.open` and `obsidian.launch.reveal` templates to `.codex/session-config.json`. The bridge will use those templates first and fall back to an `obsidian://` launch URI if no CLI template is configured.
|
|
88
127
|
|
|
89
128
|
### Workflows
|
|
90
129
|
|
|
@@ -100,7 +139,7 @@ codex-arsenal/
|
|
|
100
139
|
lib/ manifest, installer, and fetcher
|
|
101
140
|
configs/ reusable editor and agent configs
|
|
102
141
|
plugins/ plugin sketches
|
|
103
|
-
prompts/ system prompt templates
|
|
142
|
+
prompts/ system prompt templates and session notes
|
|
104
143
|
skills/ reusable agent skills
|
|
105
144
|
workflows/ repeatable agentic workflows
|
|
106
145
|
references/ curated references and notes
|
|
@@ -153,6 +192,19 @@ git push --follow-tags
|
|
|
153
192
|
|
|
154
193
|
The publish workflow runs on `v*` tags and publishes with OIDC, so it does not require a long-lived `NPM_TOKEN`.
|
|
155
194
|
|
|
195
|
+
## Codex Plugin Distribution
|
|
196
|
+
|
|
197
|
+
The npm package is the current public distribution channel. Codex plugin library publishing is separate from npm and currently does not expose a general public submission flow in the public docs.
|
|
198
|
+
|
|
199
|
+
For now, use one of these paths:
|
|
200
|
+
|
|
201
|
+
- publish reusable files through `codex-arsenal` on npm;
|
|
202
|
+
- keep plugin sketches under `plugins/`;
|
|
203
|
+
- create a local or team marketplace file at `.agents/plugins/marketplace.json` when you want Codex app users to install a repo plugin from a local marketplace;
|
|
204
|
+
- track OpenAI's Codex plugin documentation for an official public marketplace submission path.
|
|
205
|
+
|
|
206
|
+
See `docs/codex-plugin-distribution.md` for the current assessment.
|
|
207
|
+
|
|
156
208
|
## Contribution Guidelines
|
|
157
209
|
|
|
158
210
|
Good additions should be:
|
package/bin/cli.js
CHANGED
|
@@ -6,9 +6,11 @@ function printHelp() {
|
|
|
6
6
|
console.log(`codex-arsenal
|
|
7
7
|
|
|
8
8
|
Usage:
|
|
9
|
-
codex-arsenal init [--yes] [--dir <path>]
|
|
9
|
+
codex-arsenal init [--yes] [--force] [--dir <path>]
|
|
10
10
|
codex-arsenal list
|
|
11
|
-
codex-arsenal get <id...> [--dir <path>]
|
|
11
|
+
codex-arsenal get <id...> [--force] [--dir <path>]
|
|
12
|
+
|
|
13
|
+
By default, existing files are skipped. Use --force to overwrite them.
|
|
12
14
|
`);
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -28,7 +30,7 @@ function nonOptionArgs(args) {
|
|
|
28
30
|
index += 1;
|
|
29
31
|
continue;
|
|
30
32
|
}
|
|
31
|
-
if (arg === "--yes" || arg === "-y") {
|
|
33
|
+
if (arg === "--yes" || arg === "-y" || arg === "--force") {
|
|
32
34
|
continue;
|
|
33
35
|
}
|
|
34
36
|
result.push(arg);
|
|
@@ -51,6 +53,7 @@ function printList() {
|
|
|
51
53
|
async function main(argv) {
|
|
52
54
|
const [command = "init", ...args] = argv;
|
|
53
55
|
const dir = readOption(args, "--dir", readOption(args, "-d", process.cwd()));
|
|
56
|
+
const force = args.includes("--force");
|
|
54
57
|
|
|
55
58
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
56
59
|
printHelp();
|
|
@@ -67,12 +70,12 @@ async function main(argv) {
|
|
|
67
70
|
if (!ids.length) {
|
|
68
71
|
throw new Error("get requires at least one item id");
|
|
69
72
|
}
|
|
70
|
-
await runInit({ preSelected: findManifestItems(ids), skipPrompt: true, dir });
|
|
73
|
+
await runInit({ preSelected: findManifestItems(ids), skipPrompt: true, dir, force });
|
|
71
74
|
return;
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
if (command === "init") {
|
|
75
|
-
await runInit({ yes: args.includes("--yes") || args.includes("-y"), dir });
|
|
78
|
+
await runInit({ yes: args.includes("--yes") || args.includes("-y"), dir, force });
|
|
76
79
|
return;
|
|
77
80
|
}
|
|
78
81
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
openObsidianNote,
|
|
4
|
+
draftObsidianArticle,
|
|
5
|
+
loadSessionConfig,
|
|
6
|
+
syncObsidianNotes
|
|
7
|
+
} from "../lib/obsidian-bridge.js";
|
|
8
|
+
|
|
9
|
+
function printHelp() {
|
|
10
|
+
console.log(`codex-obsidian-bridge
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
codex-obsidian-bridge sync [--dir <path>] [--force]
|
|
14
|
+
codex-obsidian-bridge open [--dir <path>] [--file <path>]
|
|
15
|
+
codex-obsidian-bridge reveal [--dir <path>] [--file <path>]
|
|
16
|
+
codex-obsidian-bridge draft --title <text> --summary <text> [options]
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--dir <path> Project root that contains .codex/session-config.json
|
|
20
|
+
--force Overwrite existing Obsidian notes
|
|
21
|
+
--file <path> Vault-relative note path to open or reveal
|
|
22
|
+
--title <text> Article title
|
|
23
|
+
--summary <text> Short summary of why the work mattered
|
|
24
|
+
--changes <items> Comma-separated list of change bullets
|
|
25
|
+
--decisions <items> Comma-separated list of decision bullets
|
|
26
|
+
--verification <items> Comma-separated list of verification bullets
|
|
27
|
+
--next-steps <items> Comma-separated list of follow-up bullets
|
|
28
|
+
--signals <items> Comma-separated list of meaningful-work signals
|
|
29
|
+
--integration-boundary Force an article when a new integration is introduced
|
|
30
|
+
`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readOption(args, name, fallback = undefined) {
|
|
34
|
+
const index = args.indexOf(name);
|
|
35
|
+
if (index === -1) {
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
return args[index + 1] || fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readListOption(args, name) {
|
|
42
|
+
const value = readOption(args, name, "");
|
|
43
|
+
if (!value) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function main(argv) {
|
|
50
|
+
const [command = "help", ...args] = argv;
|
|
51
|
+
const cwd = readOption(args, "--dir", process.cwd());
|
|
52
|
+
const force = args.includes("--force");
|
|
53
|
+
|
|
54
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
55
|
+
printHelp();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const config = await loadSessionConfig(cwd);
|
|
60
|
+
|
|
61
|
+
if (command === "sync") {
|
|
62
|
+
const result = await syncObsidianNotes({ cwd, config, force });
|
|
63
|
+
for (const file of result.synced) {
|
|
64
|
+
console.log(`synced ${file}`);
|
|
65
|
+
}
|
|
66
|
+
for (const file of result.skipped) {
|
|
67
|
+
console.log(`skipped ${file} (already exists; use --force to overwrite)`);
|
|
68
|
+
}
|
|
69
|
+
console.log(`target vault: ${result.targetVault}`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (command === "open" || command === "reveal") {
|
|
74
|
+
const result = await openObsidianNote({
|
|
75
|
+
cwd,
|
|
76
|
+
config,
|
|
77
|
+
filePath: readOption(args, "--file"),
|
|
78
|
+
mode: command
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!result.launched) {
|
|
82
|
+
console.log("Obsidian is disabled for this project.");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(result.mode === "command" ? "launched configured Obsidian command" : `opened ${result.spec.uri}`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (command === "draft") {
|
|
91
|
+
const result = await draftObsidianArticle({
|
|
92
|
+
cwd,
|
|
93
|
+
config,
|
|
94
|
+
title: readOption(args, "--title"),
|
|
95
|
+
summary: readOption(args, "--summary"),
|
|
96
|
+
changes: readListOption(args, "--changes"),
|
|
97
|
+
decisions: readListOption(args, "--decisions"),
|
|
98
|
+
verification: readListOption(args, "--verification"),
|
|
99
|
+
nextSteps: readListOption(args, "--next-steps"),
|
|
100
|
+
signals: readListOption(args, "--signals"),
|
|
101
|
+
integrationBoundary: args.includes("--integration-boundary"),
|
|
102
|
+
force
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (result.skipped) {
|
|
106
|
+
console.log("no article drafted");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(`drafted ${result.written}`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw new Error(`Unknown command: ${command}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
118
|
+
console.error(error.message);
|
|
119
|
+
process.exitCode = 1;
|
|
120
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sessionInitializer": "prompts/session-bootstrap.md",
|
|
3
|
+
"codeStyleNote": "prompts/code-style.md",
|
|
4
|
+
"meaningfulWorkNote": "prompts/meaningful-work.md",
|
|
5
|
+
"obsidian": {
|
|
6
|
+
"enabled": true,
|
|
7
|
+
"cliCommand": "obsidian",
|
|
8
|
+
"vaultPath": "~/Obsidian/Codex",
|
|
9
|
+
"inboxPath": "Inbox/Codex",
|
|
10
|
+
"launch": {
|
|
11
|
+
"open": null,
|
|
12
|
+
"reveal": null
|
|
13
|
+
},
|
|
14
|
+
"noteTargets": {
|
|
15
|
+
"sessionInitializer": "Codex/Session-Initializer.md",
|
|
16
|
+
"codeStyle": "Codex/Code-Style.md",
|
|
17
|
+
"meaningfulWork": "Codex/Meaningful-Work.md"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"articleRules": {
|
|
21
|
+
"highSignalTriggers": [
|
|
22
|
+
"new or changed installable content",
|
|
23
|
+
"behavioral change",
|
|
24
|
+
"external integration",
|
|
25
|
+
"substantive README or workflow update"
|
|
26
|
+
],
|
|
27
|
+
"minimumSignalsForDraft": 2,
|
|
28
|
+
"requiresVerification": true
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Codex Plugin Distribution
|
|
2
|
+
|
|
3
|
+
## Current Assessment
|
|
4
|
+
|
|
5
|
+
As of May 23, 2026, the practical public distribution path for Codex-Arsenal is npm. OpenAI's public Codex material says Codex users can browse the plugins library and create plugins in the Codex app, but it does not document a general public submission process for third-party plugins.
|
|
6
|
+
|
|
7
|
+
Relevant OpenAI pages:
|
|
8
|
+
|
|
9
|
+
- [Plugins and skills](https://openai.com/academy/codex-plugins-and-skills/)
|
|
10
|
+
- [Codex](https://openai.com/codex/)
|
|
11
|
+
- [Using Codex with your ChatGPT plan](https://help.openai.com/en/articles/11369540/)
|
|
12
|
+
|
|
13
|
+
## Recommended Path
|
|
14
|
+
|
|
15
|
+
1. Keep publishing installable skills, prompts, configs, and workflow files through `codex-arsenal` on npm.
|
|
16
|
+
2. Keep plugin ideas in `plugins/<name>/` until they become real Codex plugins.
|
|
17
|
+
3. When a plugin is ready for Codex app testing, convert it to a Codex plugin structure with `.codex-plugin/plugin.json`.
|
|
18
|
+
4. Add a repo-local marketplace entry in `.agents/plugins/marketplace.json` for team/local installation.
|
|
19
|
+
5. If OpenAI opens a formal public marketplace submission path, package the plugin manifest, documentation, permission model, and test notes for review.
|
|
20
|
+
|
|
21
|
+
## Repo Marketplace Shape
|
|
22
|
+
|
|
23
|
+
Use this shape for local or team discovery:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"name": "codex-arsenal",
|
|
28
|
+
"interface": {
|
|
29
|
+
"displayName": "Codex-Arsenal"
|
|
30
|
+
},
|
|
31
|
+
"plugins": [
|
|
32
|
+
{
|
|
33
|
+
"name": "context-window-compressor",
|
|
34
|
+
"source": {
|
|
35
|
+
"source": "local",
|
|
36
|
+
"path": "./plugins/context-window-compressor"
|
|
37
|
+
},
|
|
38
|
+
"policy": {
|
|
39
|
+
"installation": "AVAILABLE",
|
|
40
|
+
"authentication": "ON_INSTALL"
|
|
41
|
+
},
|
|
42
|
+
"category": "Productivity"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Before Submitting Anywhere
|
|
49
|
+
|
|
50
|
+
- Define exactly what external systems the plugin touches.
|
|
51
|
+
- Keep permissions narrow and explain authentication.
|
|
52
|
+
- Provide a small demo workflow and screenshots if the plugin has UI.
|
|
53
|
+
- Include test notes for install, auth, failure states, and uninstall.
|
|
54
|
+
- Avoid bundling broad arsenals into one plugin. Publish focused plugins that solve one job well.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# npm Publishing
|
|
2
|
+
|
|
3
|
+
This package is set up for npm Trusted Publishing from GitHub Actions.
|
|
4
|
+
|
|
5
|
+
## One-time npm setup
|
|
6
|
+
|
|
7
|
+
1. Create or claim the `codex-arsenal` package on npm.
|
|
8
|
+
2. On npmjs.com, open the package settings and add a Trusted Publisher:
|
|
9
|
+
- Provider: GitHub Actions
|
|
10
|
+
- Owner: `citron03`
|
|
11
|
+
- Repository: `Codex-Arsenal`
|
|
12
|
+
- Workflow filename: `publish.yml`
|
|
13
|
+
- Environment: `npm`
|
|
14
|
+
3. After the first trusted publish works, set publishing access to require 2FA and disallow tokens.
|
|
15
|
+
|
|
16
|
+
## Release process
|
|
17
|
+
|
|
18
|
+
1. Ensure `main` is green.
|
|
19
|
+
2. Update the version:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm version patch
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
3. Push the commit and tag:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git push --follow-tags
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
GitHub Actions will run tests, perform a package dry run, and publish to npm from the `v*` tag.
|
|
32
|
+
|
|
33
|
+
## Why Trusted Publishing
|
|
34
|
+
|
|
35
|
+
Trusted Publishing uses short-lived OIDC credentials from GitHub Actions instead of a long-lived `NPM_TOKEN`. For public packages published from public repositories, npm also creates provenance attestations automatically.
|
package/lib/installer.js
CHANGED
|
@@ -2,12 +2,15 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
import { stdin as input, stdout as output } from "node:process";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { BASE_URL, MANIFEST } from "./manifest.js";
|
|
6
7
|
import { fetchFile } from "./fetcher.js";
|
|
7
8
|
|
|
9
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
|
|
8
11
|
async function readSourceFile(src) {
|
|
9
12
|
try {
|
|
10
|
-
return await readFile(src, "utf8");
|
|
13
|
+
return await readFile(path.join(PACKAGE_ROOT, src), "utf8");
|
|
11
14
|
} catch (error) {
|
|
12
15
|
if (error.code !== "ENOENT") {
|
|
13
16
|
throw error;
|
|
@@ -21,13 +24,27 @@ export async function installItems(items, targetDir, opts = {}) {
|
|
|
21
24
|
let installed = 0;
|
|
22
25
|
let failed = 0;
|
|
23
26
|
const successes = [];
|
|
27
|
+
const skipped = [];
|
|
24
28
|
const failures = [];
|
|
25
29
|
const readSource = opts.readSourceFile || readSourceFile;
|
|
30
|
+
const force = !!opts.force;
|
|
26
31
|
|
|
27
32
|
for (const item of items) {
|
|
28
33
|
for (const file of item.files) {
|
|
29
34
|
const destination = path.join(resolvedTarget, file.dest);
|
|
30
35
|
try {
|
|
36
|
+
if (!force) {
|
|
37
|
+
try {
|
|
38
|
+
await readFile(destination, "utf8");
|
|
39
|
+
skipped.push(file.dest);
|
|
40
|
+
continue;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.code !== "ENOENT") {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
const content = await readSource(file.src);
|
|
32
49
|
await mkdir(path.dirname(destination), { recursive: true });
|
|
33
50
|
await writeFile(destination, content, "utf8");
|
|
@@ -40,7 +57,7 @@ export async function installItems(items, targetDir, opts = {}) {
|
|
|
40
57
|
}
|
|
41
58
|
}
|
|
42
59
|
|
|
43
|
-
return { installed, failed, successes, failures, targetDir: resolvedTarget };
|
|
60
|
+
return { installed, failed, successes, skipped, failures, targetDir: resolvedTarget };
|
|
44
61
|
}
|
|
45
62
|
|
|
46
63
|
function defaultItems() {
|
|
@@ -78,18 +95,24 @@ export async function runInit(opts = {}) {
|
|
|
78
95
|
|
|
79
96
|
if (!selected.length) {
|
|
80
97
|
console.log("No items selected.");
|
|
81
|
-
return { installed: 0, failed: 0, successes: [], failures: [], targetDir: path.resolve(opts.dir || ".") };
|
|
98
|
+
return { installed: 0, failed: 0, successes: [], skipped: [], failures: [], targetDir: path.resolve(opts.dir || ".") };
|
|
82
99
|
}
|
|
83
100
|
|
|
84
101
|
const result = await installItems(selected, opts.dir || process.cwd(), {
|
|
85
|
-
readSourceFile: opts.readSourceFile
|
|
102
|
+
readSourceFile: opts.readSourceFile,
|
|
103
|
+
force: opts.force
|
|
86
104
|
});
|
|
87
105
|
for (const file of result.successes) {
|
|
88
106
|
console.log(`installed ${file}`);
|
|
89
107
|
}
|
|
108
|
+
for (const file of result.skipped) {
|
|
109
|
+
console.log(`skipped ${file} (already exists; use --force to overwrite)`);
|
|
110
|
+
}
|
|
90
111
|
for (const failure of result.failures) {
|
|
91
112
|
console.log(`failed ${failure.dest} (${failure.error})`);
|
|
92
113
|
}
|
|
93
|
-
console.log(
|
|
114
|
+
console.log(
|
|
115
|
+
`\nDone: ${result.installed} installed${result.skipped.length ? `, ${result.skipped.length} skipped` : ""}${result.failed ? `, ${result.failed} failed` : ""}.`
|
|
116
|
+
);
|
|
94
117
|
return result;
|
|
95
118
|
}
|
package/lib/manifest.js
CHANGED
|
@@ -11,13 +11,6 @@ export const MANIFEST = [
|
|
|
11
11
|
files: [{ src: "CODEX.md", dest: "CODEX.md" }],
|
|
12
12
|
default: true
|
|
13
13
|
},
|
|
14
|
-
{
|
|
15
|
-
id: "claude-md",
|
|
16
|
-
category: "Behavior Guidelines",
|
|
17
|
-
label: "CLAUDE.md",
|
|
18
|
-
description: "Behavior guidelines adapted for Claude Code agents.",
|
|
19
|
-
files: [{ src: "CLAUDE.md", dest: "CLAUDE.md" }]
|
|
20
|
-
},
|
|
21
14
|
{
|
|
22
15
|
id: "config-codex",
|
|
23
16
|
category: "Configs",
|
|
@@ -25,6 +18,13 @@ export const MANIFEST = [
|
|
|
25
18
|
description: "Starter Codex agent configuration.",
|
|
26
19
|
files: [{ src: "configs/codex/config.json", dest: ".codex/config.json" }]
|
|
27
20
|
},
|
|
21
|
+
{
|
|
22
|
+
id: "config-session-bootstrap",
|
|
23
|
+
category: "Configs",
|
|
24
|
+
label: ".codex/session-config.json",
|
|
25
|
+
description: "Session bootstrap and Obsidian bridge settings.",
|
|
26
|
+
files: [{ src: "configs/codex/session-config.json", dest: ".codex/session-config.json" }]
|
|
27
|
+
},
|
|
28
28
|
{
|
|
29
29
|
id: "config-vscode",
|
|
30
30
|
category: "Configs",
|
|
@@ -32,6 +32,27 @@ export const MANIFEST = [
|
|
|
32
32
|
description: "VS Code settings for AI-assisted development.",
|
|
33
33
|
files: [{ src: "configs/vscode/settings.json", dest: ".vscode/settings.json" }]
|
|
34
34
|
},
|
|
35
|
+
{
|
|
36
|
+
id: "prompt-session-bootstrap",
|
|
37
|
+
category: "Prompts",
|
|
38
|
+
label: "session-bootstrap",
|
|
39
|
+
description: "Session startup instructions for Codex.",
|
|
40
|
+
files: [{ src: "prompts/session-bootstrap.md", dest: "prompts/session-bootstrap.md" }]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "prompt-code-style",
|
|
44
|
+
category: "Prompts",
|
|
45
|
+
label: "code-style",
|
|
46
|
+
description: "Obsidian-backed code style note template.",
|
|
47
|
+
files: [{ src: "prompts/code-style.md", dest: "prompts/code-style.md" }]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "prompt-meaningful-work",
|
|
51
|
+
category: "Prompts",
|
|
52
|
+
label: "meaningful-work",
|
|
53
|
+
description: "Criteria and article template for meaningful Codex work.",
|
|
54
|
+
files: [{ src: "prompts/meaningful-work.md", dest: "prompts/meaningful-work.md" }]
|
|
55
|
+
},
|
|
35
56
|
{
|
|
36
57
|
id: "prompt-solo-dev",
|
|
37
58
|
category: "Prompts",
|
|
@@ -69,6 +90,15 @@ export const MANIFEST = [
|
|
|
69
90
|
{ src: "skills/publishing-npm-packages/SKILL.md", dest: "skills/publishing-npm-packages/SKILL.md" }
|
|
70
91
|
]
|
|
71
92
|
},
|
|
93
|
+
{
|
|
94
|
+
id: "skill-obsidian-session-loop",
|
|
95
|
+
category: "Skills",
|
|
96
|
+
label: "obsidian-session-loop",
|
|
97
|
+
description: "A Codex skill for session startup, Obsidian sync, and end-of-session article drafting.",
|
|
98
|
+
files: [
|
|
99
|
+
{ src: "skills/obsidian-session-loop/SKILL.md", dest: "skills/obsidian-session-loop/SKILL.md" }
|
|
100
|
+
]
|
|
101
|
+
},
|
|
72
102
|
{
|
|
73
103
|
id: "plugin-context-window-compressor",
|
|
74
104
|
category: "Plugins",
|
|
@@ -79,6 +109,16 @@ export const MANIFEST = [
|
|
|
79
109
|
{ src: "plugins/context-window-compressor/plugin.json", dest: "plugins/context-window-compressor/plugin.json" }
|
|
80
110
|
]
|
|
81
111
|
},
|
|
112
|
+
{
|
|
113
|
+
id: "plugin-obsidian-codex-bridge",
|
|
114
|
+
category: "Plugins",
|
|
115
|
+
label: "obsidian-codex-bridge",
|
|
116
|
+
description: "Plugin sketch for syncing Codex notes and article drafts with Obsidian.",
|
|
117
|
+
files: [
|
|
118
|
+
{ src: "plugins/obsidian-codex-bridge/README.md", dest: "plugins/obsidian-codex-bridge/README.md" },
|
|
119
|
+
{ src: "plugins/obsidian-codex-bridge/plugin.json", dest: "plugins/obsidian-codex-bridge/plugin.json" }
|
|
120
|
+
]
|
|
121
|
+
},
|
|
82
122
|
{
|
|
83
123
|
id: "workflow-solo-dev-loop",
|
|
84
124
|
category: "Workflows",
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
|
|
7
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
|
|
10
|
+
export function resolveConfigPath(cwd = process.cwd()) {
|
|
11
|
+
return path.join(cwd, ".codex", "session-config.json");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function resolvePackageAsset(...segments) {
|
|
15
|
+
return path.join(PACKAGE_ROOT, ...segments);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function defaultNoteTargets() {
|
|
19
|
+
return {
|
|
20
|
+
sessionInitializer: "Codex/Session-Initializer.md",
|
|
21
|
+
codeStyle: "Codex/Code-Style.md",
|
|
22
|
+
meaningfulWork: "Codex/Meaningful-Work.md"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function defaultLaunchTemplates() {
|
|
27
|
+
return {
|
|
28
|
+
open: null,
|
|
29
|
+
reveal: null
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function normalizeVaultPath(vaultPath, cwd = process.cwd()) {
|
|
34
|
+
if (!vaultPath) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (vaultPath.startsWith("~")) {
|
|
38
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
39
|
+
return path.resolve(homeDir, vaultPath.slice(1).replace(/^\//, ""));
|
|
40
|
+
}
|
|
41
|
+
return path.resolve(cwd, vaultPath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getVaultName(vaultPath) {
|
|
45
|
+
return path.basename(vaultPath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function slugifyTitle(title) {
|
|
49
|
+
return title
|
|
50
|
+
.toLowerCase()
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
53
|
+
.replace(/^-+|-+$/g, "")
|
|
54
|
+
.slice(0, 72) || "session-note";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function isoDateInSeoul(date = new Date()) {
|
|
58
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
59
|
+
timeZone: "Asia/Seoul",
|
|
60
|
+
year: "numeric",
|
|
61
|
+
month: "2-digit",
|
|
62
|
+
day: "2-digit"
|
|
63
|
+
}).format(date);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function readJsonFile(filePath) {
|
|
67
|
+
const raw = await readFile(filePath, "utf8");
|
|
68
|
+
return JSON.parse(raw);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeTemplateValue(value, context) {
|
|
72
|
+
if (typeof value === "string") {
|
|
73
|
+
return value
|
|
74
|
+
.replaceAll("{vault}", context.vaultName)
|
|
75
|
+
.replaceAll("{vaultPath}", context.vaultRoot)
|
|
76
|
+
.replaceAll("{file}", context.filePath)
|
|
77
|
+
.replaceAll("{mode}", context.mode);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
return value.map((item) => normalizeTemplateValue(item, context));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function resolveObsidianLaunchSpec({
|
|
88
|
+
cwd = process.cwd(),
|
|
89
|
+
config,
|
|
90
|
+
filePath,
|
|
91
|
+
mode = "open"
|
|
92
|
+
} = {}) {
|
|
93
|
+
const resolvedConfig = config || {};
|
|
94
|
+
const obsidian = resolvedConfig.obsidian || {};
|
|
95
|
+
if (!obsidian.enabled) {
|
|
96
|
+
return { type: "disabled" };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const vaultRoot = normalizeVaultPath(obsidian.vaultPath, cwd);
|
|
100
|
+
const vaultName = getVaultName(vaultRoot);
|
|
101
|
+
const notePath = filePath || obsidian.noteTargets?.sessionInitializer || defaultNoteTargets().sessionInitializer;
|
|
102
|
+
const absoluteNotePath = path.isAbsolute(notePath) ? notePath : path.join(vaultRoot, notePath);
|
|
103
|
+
const relativeFilePath = path.relative(vaultRoot, absoluteNotePath);
|
|
104
|
+
|
|
105
|
+
if (relativeFilePath.startsWith("..")) {
|
|
106
|
+
throw new Error(`Obsidian note must be inside the vault: ${notePath}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const templates = {
|
|
110
|
+
...defaultLaunchTemplates(),
|
|
111
|
+
...(obsidian.launch || {})
|
|
112
|
+
};
|
|
113
|
+
const template = templates[mode];
|
|
114
|
+
|
|
115
|
+
if (template) {
|
|
116
|
+
return {
|
|
117
|
+
type: "command",
|
|
118
|
+
command: template.command || obsidian.cliCommand || "obsidian",
|
|
119
|
+
args: normalizeTemplateValue(template.args || [], {
|
|
120
|
+
vaultName,
|
|
121
|
+
vaultRoot,
|
|
122
|
+
filePath: relativeFilePath,
|
|
123
|
+
mode
|
|
124
|
+
}),
|
|
125
|
+
cwd: vaultRoot
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
type: "uri",
|
|
131
|
+
uri: `obsidian://open?vault=${encodeURIComponent(vaultName)}&file=${encodeURIComponent(relativeFilePath)}`
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function launchObsidianTarget(spec, runner = execFileAsync) {
|
|
136
|
+
if (spec.type === "disabled") {
|
|
137
|
+
return { launched: false, mode: "disabled" };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (spec.type === "command") {
|
|
141
|
+
await runner(spec.command, spec.args, { cwd: spec.cwd });
|
|
142
|
+
return { launched: true, mode: "command" };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (spec.type === "uri") {
|
|
146
|
+
if (process.platform === "darwin") {
|
|
147
|
+
await runner("open", [spec.uri]);
|
|
148
|
+
} else if (process.platform === "linux") {
|
|
149
|
+
await runner("xdg-open", [spec.uri]);
|
|
150
|
+
} else if (process.platform === "win32") {
|
|
151
|
+
await runner("cmd", ["/c", "start", "", spec.uri]);
|
|
152
|
+
} else {
|
|
153
|
+
throw new Error(`Unsupported platform for launching Obsidian: ${process.platform}`);
|
|
154
|
+
}
|
|
155
|
+
return { launched: true, mode: "uri" };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
throw new Error(`Unknown Obsidian launch spec type: ${spec.type}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function loadSessionConfig(cwd = process.cwd(), readJson = readJsonFile) {
|
|
162
|
+
const projectConfigPath = resolveConfigPath(cwd);
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
return await readJson(projectConfigPath);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error.code !== "ENOENT") {
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const packageConfigPath = resolvePackageAsset("configs", "codex", "session-config.json");
|
|
173
|
+
return readJson(packageConfigPath);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function shouldDraftArticle({ signals = [], config, integrationBoundary = false }) {
|
|
177
|
+
if (integrationBoundary) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const minimumSignals = config?.articleRules?.minimumSignalsForDraft ?? 2;
|
|
182
|
+
return signals.length >= minimumSignals;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function buildArticleMarkdown({
|
|
186
|
+
title,
|
|
187
|
+
summary,
|
|
188
|
+
changes = [],
|
|
189
|
+
decisions = [],
|
|
190
|
+
verification = [],
|
|
191
|
+
nextSteps = [],
|
|
192
|
+
signals = []
|
|
193
|
+
}) {
|
|
194
|
+
const sections = [
|
|
195
|
+
`# ${title}`,
|
|
196
|
+
"",
|
|
197
|
+
`- Date: ${isoDateInSeoul()}`,
|
|
198
|
+
signals.length ? `- Signals: ${signals.join(", ")}` : null,
|
|
199
|
+
"",
|
|
200
|
+
"## Why It Mattered",
|
|
201
|
+
summary || "TBD",
|
|
202
|
+
"",
|
|
203
|
+
"## What Changed",
|
|
204
|
+
...(changes.length ? changes.map((change) => `- ${change}`) : ["- TBD"]),
|
|
205
|
+
"",
|
|
206
|
+
"## Decisions",
|
|
207
|
+
...(decisions.length ? decisions.map((decision) => `- ${decision}`) : ["- TBD"]),
|
|
208
|
+
"",
|
|
209
|
+
"## Verification",
|
|
210
|
+
...(verification.length ? verification.map((item) => `- ${item}`) : ["- TBD"]),
|
|
211
|
+
"",
|
|
212
|
+
"## Next Steps",
|
|
213
|
+
...(nextSteps.length ? nextSteps.map((step) => `- ${step}`) : ["- TBD"])
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
return sections.filter((line) => line !== null).join("\n");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function readSourceMarkdown(relativePath, cwd = process.cwd()) {
|
|
220
|
+
if (path.isAbsolute(relativePath)) {
|
|
221
|
+
return readFile(relativePath, "utf8");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const localPath = path.resolve(cwd, relativePath);
|
|
225
|
+
try {
|
|
226
|
+
return await readFile(localPath, "utf8");
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (error.code !== "ENOENT") {
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return readFile(resolvePackageAsset(relativePath), "utf8");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function ensureDirectoryFor(filePath) {
|
|
237
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function writeIfNeeded(destination, content, force) {
|
|
241
|
+
if (!force) {
|
|
242
|
+
try {
|
|
243
|
+
await readFile(destination, "utf8");
|
|
244
|
+
return "skipped";
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (error.code !== "ENOENT") {
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await ensureDirectoryFor(destination);
|
|
253
|
+
await writeFile(destination, content, "utf8");
|
|
254
|
+
return "written";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function syncObsidianNotes({ cwd = process.cwd(), config, force = false, readSource = readSourceMarkdown } = {}) {
|
|
258
|
+
const resolvedConfig = config || (await loadSessionConfig(cwd));
|
|
259
|
+
const obsidian = resolvedConfig.obsidian || {};
|
|
260
|
+
if (!obsidian.enabled) {
|
|
261
|
+
return { synced: [], skipped: [], targetVault: null };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const vaultRoot = normalizeVaultPath(obsidian.vaultPath, cwd);
|
|
265
|
+
const targets = {
|
|
266
|
+
...defaultNoteTargets(),
|
|
267
|
+
...(obsidian.noteTargets || {})
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const notes = [
|
|
271
|
+
{
|
|
272
|
+
src: resolvedConfig.sessionInitializer || "prompts/session-bootstrap.md",
|
|
273
|
+
dest: targets.sessionInitializer
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
src: resolvedConfig.codeStyleNote || "prompts/code-style.md",
|
|
277
|
+
dest: targets.codeStyle
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
src: resolvedConfig.meaningfulWorkNote || "prompts/meaningful-work.md",
|
|
281
|
+
dest: targets.meaningfulWork
|
|
282
|
+
}
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
const synced = [];
|
|
286
|
+
const skipped = [];
|
|
287
|
+
|
|
288
|
+
for (const note of notes) {
|
|
289
|
+
const sourceContent = await readSource(note.src, cwd);
|
|
290
|
+
const destination = path.join(vaultRoot, note.dest);
|
|
291
|
+
const status = await writeIfNeeded(destination, sourceContent, force);
|
|
292
|
+
if (status === "written") {
|
|
293
|
+
synced.push(note.dest);
|
|
294
|
+
} else {
|
|
295
|
+
skipped.push(note.dest);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { synced, skipped, targetVault: vaultRoot };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function draftObsidianArticle({
|
|
303
|
+
cwd = process.cwd(),
|
|
304
|
+
config,
|
|
305
|
+
title,
|
|
306
|
+
summary,
|
|
307
|
+
changes = [],
|
|
308
|
+
decisions = [],
|
|
309
|
+
verification = [],
|
|
310
|
+
nextSteps = [],
|
|
311
|
+
signals = [],
|
|
312
|
+
integrationBoundary = false,
|
|
313
|
+
force = false
|
|
314
|
+
} = {}) {
|
|
315
|
+
const resolvedConfig = config || (await loadSessionConfig(cwd));
|
|
316
|
+
const obsidian = resolvedConfig.obsidian || {};
|
|
317
|
+
if (!obsidian.enabled) {
|
|
318
|
+
return { written: null, skipped: true, targetVault: null };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (!shouldDraftArticle({ signals, config: resolvedConfig, integrationBoundary })) {
|
|
322
|
+
return { written: null, skipped: true, targetVault: normalizeVaultPath(obsidian.vaultPath, cwd) };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const vaultRoot = normalizeVaultPath(obsidian.vaultPath, cwd);
|
|
326
|
+
const inboxPath = obsidian.inboxPath || "Inbox/Codex";
|
|
327
|
+
const articleTitle = title || "Codex Session Article";
|
|
328
|
+
const fileName = `${isoDateInSeoul()}-${slugifyTitle(articleTitle)}.md`;
|
|
329
|
+
const destination = path.join(vaultRoot, inboxPath, fileName);
|
|
330
|
+
const content = buildArticleMarkdown({
|
|
331
|
+
title: articleTitle,
|
|
332
|
+
summary,
|
|
333
|
+
changes,
|
|
334
|
+
decisions,
|
|
335
|
+
verification,
|
|
336
|
+
nextSteps,
|
|
337
|
+
signals
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const status = await writeIfNeeded(destination, content, force);
|
|
341
|
+
return { written: status === "written" ? path.relative(vaultRoot, destination) : null, skipped: status !== "written", targetVault: vaultRoot };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function openObsidianNote({
|
|
345
|
+
cwd = process.cwd(),
|
|
346
|
+
config,
|
|
347
|
+
filePath,
|
|
348
|
+
mode = "open",
|
|
349
|
+
runner
|
|
350
|
+
} = {}) {
|
|
351
|
+
const spec = resolveObsidianLaunchSpec({ cwd, config, filePath, mode });
|
|
352
|
+
const result = await launchObsidianTarget(spec, runner);
|
|
353
|
+
return { ...result, spec };
|
|
354
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-arsenal",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A practical arsenal of Codex
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "A practical arsenal of Codex plugins, skills, prompts, workflows, and configs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"codex-arsenal": "bin/cli.js"
|
|
7
|
+
"codex-arsenal": "bin/cli.js",
|
|
8
|
+
"codex-obsidian-bridge": "bin/obsidian-bridge.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
11
|
"test": "node --test"
|
|
@@ -22,9 +23,10 @@
|
|
|
22
23
|
},
|
|
23
24
|
"files": [
|
|
24
25
|
"bin/",
|
|
26
|
+
"docs/codex-plugin-distribution.md",
|
|
27
|
+
"docs/npm-publishing.md",
|
|
25
28
|
"lib/",
|
|
26
29
|
"CODEX.md",
|
|
27
|
-
"CLAUDE.md",
|
|
28
30
|
"configs/",
|
|
29
31
|
"plugins/",
|
|
30
32
|
"prompts/",
|
|
@@ -36,7 +38,6 @@
|
|
|
36
38
|
},
|
|
37
39
|
"keywords": [
|
|
38
40
|
"codex",
|
|
39
|
-
"claude-code",
|
|
40
41
|
"ai-agent",
|
|
41
42
|
"developer-tools",
|
|
42
43
|
"prompt-engineering"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# obsidian-codex-bridge
|
|
2
|
+
|
|
3
|
+
Plugin sketch for connecting Codex sessions to an Obsidian vault.
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Make the session bootstrap, code-style note, and end-of-session article draft part of one loop instead of separate manual steps.
|
|
8
|
+
|
|
9
|
+
## Proposed Modes
|
|
10
|
+
|
|
11
|
+
- `bootstrap`: read the configured session note and merge project settings.
|
|
12
|
+
- `sync`: copy or mirror the latest code-style and session notes into the vault.
|
|
13
|
+
- `draft-article`: write a session article into the Obsidian inbox when the meaningful-work criteria are met.
|
|
14
|
+
|
|
15
|
+
## Suggested Settings
|
|
16
|
+
|
|
17
|
+
- `configs/codex/session-config.json`
|
|
18
|
+
- `prompts/session-bootstrap.md`
|
|
19
|
+
- `prompts/code-style.md`
|
|
20
|
+
- `prompts/meaningful-work.md`
|
|
21
|
+
|
|
22
|
+
## Obsidian CLI
|
|
23
|
+
|
|
24
|
+
If an `obsidian` command is available, the bridge can use it to open the vault, reveal the generated note, or hand off to a vault-local workflow. If the CLI is not available, keep the markdown files as the source of truth and mirror them manually.
|
|
25
|
+
|
|
26
|
+
## Practical CLI
|
|
27
|
+
|
|
28
|
+
The repository now ships a small `codex-obsidian-bridge` command that can sync the notes, open or reveal a note, and draft an article into the configured inbox.
|
|
29
|
+
|
|
30
|
+
If a project has an Obsidian CLI, configure `obsidian.launch.open` and `obsidian.launch.reveal` in `.codex/session-config.json`. Otherwise the bridge falls back to an `obsidian://` URI launch.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Code Style Note
|
|
2
|
+
|
|
3
|
+
This note is meant to live in Obsidian and flow back into session startup.
|
|
4
|
+
|
|
5
|
+
## Baseline Style
|
|
6
|
+
|
|
7
|
+
- Prefer small, direct modules with one responsibility.
|
|
8
|
+
- Keep names explicit and stable.
|
|
9
|
+
- Favor readable control flow over clever abstractions.
|
|
10
|
+
- Match the repository's existing style before introducing new patterns.
|
|
11
|
+
- Keep comments rare and only use them when the intent would otherwise be hard to follow.
|
|
12
|
+
|
|
13
|
+
## Example Source of Inspiration
|
|
14
|
+
|
|
15
|
+
Use widely adopted linting conventions such as Airbnb-style JavaScript guidance as a reference point, but adapt the final rules to the current repository.
|
|
16
|
+
|
|
17
|
+
## Project-Specific Overrides
|
|
18
|
+
|
|
19
|
+
- Document any exceptions here before asking the agent to apply them.
|
|
20
|
+
- If a project wants a different style for a specific directory, note that directory here.
|
|
21
|
+
- Keep the note short enough that it can be read at the start of every session.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Meaningful Work Criteria
|
|
2
|
+
|
|
3
|
+
Use these criteria to decide when a Codex session should generate an Obsidian article at the end of the task.
|
|
4
|
+
|
|
5
|
+
## Trigger Rules
|
|
6
|
+
|
|
7
|
+
Draft an article when the session meets at least two of these signals:
|
|
8
|
+
|
|
9
|
+
- the change touches two or more repository files in one coherent slice;
|
|
10
|
+
- the task introduces or changes installable content, configs, or workflows;
|
|
11
|
+
- the work changes behavior, not just copy or formatting;
|
|
12
|
+
- the change requires verification beyond a quick smoke check;
|
|
13
|
+
- the task includes a substantive README or documentation update.
|
|
14
|
+
|
|
15
|
+
Always draft an article when the session introduces a new integration boundary, such as a bridge to Obsidian or another external tool.
|
|
16
|
+
|
|
17
|
+
## Article Template
|
|
18
|
+
|
|
19
|
+
Use this outline for the draft:
|
|
20
|
+
|
|
21
|
+
1. Title
|
|
22
|
+
2. Why the change mattered
|
|
23
|
+
3. What changed
|
|
24
|
+
4. Decisions and tradeoffs
|
|
25
|
+
5. Verification performed
|
|
26
|
+
6. What to revisit later
|
|
27
|
+
|
|
28
|
+
## Writing Rule
|
|
29
|
+
|
|
30
|
+
Keep the article concrete. Write about the decision, the structure, and the follow-up work, not just the file list.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Session Bootstrap
|
|
2
|
+
|
|
3
|
+
Use this file as the first project-local note after `CODEX.md`.
|
|
4
|
+
|
|
5
|
+
## Startup Order
|
|
6
|
+
|
|
7
|
+
1. Read `configs/codex/session-config.json` if it exists.
|
|
8
|
+
2. Load the configured session initializer, code style note, and meaningful-work note.
|
|
9
|
+
3. Merge the current task with the project-specific style guidance.
|
|
10
|
+
4. Check whether Obsidian integration is available before drafting session notes.
|
|
11
|
+
5. If the bridge is available, sync the session notes before starting the task.
|
|
12
|
+
|
|
13
|
+
## What This Session Needs
|
|
14
|
+
|
|
15
|
+
- Keep a short, explicit summary of the current goal.
|
|
16
|
+
- Keep the active assumptions visible.
|
|
17
|
+
- Prefer the simplest change that still satisfies the task.
|
|
18
|
+
- Treat the configured code-style note as the source of truth for formatting and tone.
|
|
19
|
+
|
|
20
|
+
## End-of-Session Rule
|
|
21
|
+
|
|
22
|
+
If the current work satisfies the meaningful-work criteria, create a draft article in the configured Obsidian inbox and summarize the result there.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: obsidian-session-loop
|
|
3
|
+
description: Use when starting or ending a Codex session that should read project-local session notes, sync Obsidian-backed style guidance, and draft an article when the work is meaningful.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Obsidian Session Loop
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Use this skill when a project keeps session guidance in `CODEX.md`, stores session settings in `.codex/session-config.json`, and mirrors the working notes into an Obsidian vault.
|
|
11
|
+
|
|
12
|
+
## When To Use
|
|
13
|
+
|
|
14
|
+
- At the start of a Codex session for this repository.
|
|
15
|
+
- When the project wants a code-style note to influence the current session.
|
|
16
|
+
- When the work should produce a session article in Obsidian.
|
|
17
|
+
- When the task touches the session bootstrap, Obsidian bridge, or the meaningful-work criteria.
|
|
18
|
+
|
|
19
|
+
## Session Startup
|
|
20
|
+
|
|
21
|
+
1. Read `CODEX.md` first.
|
|
22
|
+
2. Load `.codex/session-config.json` if it exists.
|
|
23
|
+
3. Read the configured session initializer, code-style note, and meaningful-work note.
|
|
24
|
+
4. Sync the notes into Obsidian if `codex-obsidian-bridge` is available.
|
|
25
|
+
5. Merge the code-style note into the current working style before editing files.
|
|
26
|
+
|
|
27
|
+
## Meaningful Work Check
|
|
28
|
+
|
|
29
|
+
Treat the work as meaningful when at least two of these are true:
|
|
30
|
+
|
|
31
|
+
- the change spans multiple files in one coherent slice;
|
|
32
|
+
- the task adds or changes installable content, configs, or workflows;
|
|
33
|
+
- the work changes behavior rather than only copy;
|
|
34
|
+
- verification is required beyond a trivial smoke test;
|
|
35
|
+
- the README or user-facing guidance needs a substantive update.
|
|
36
|
+
|
|
37
|
+
Always treat new integration boundaries as meaningful, including the Obsidian bridge itself.
|
|
38
|
+
|
|
39
|
+
## End Of Session
|
|
40
|
+
|
|
41
|
+
1. Summarize the actual change, not the file list.
|
|
42
|
+
2. If the meaningful-work check passes, draft an Obsidian article with the bridge CLI.
|
|
43
|
+
3. Include what changed, why it mattered, the tradeoffs, verification, and follow-up work.
|
|
44
|
+
|
|
45
|
+
## Guardrails
|
|
46
|
+
|
|
47
|
+
- Keep the session bootstrap short and readable.
|
|
48
|
+
- Prefer the configured note paths over hardcoded vault locations.
|
|
49
|
+
- Do not draft an article for tiny formatting-only changes.
|
|
50
|
+
- Do not overwrite a user’s Obsidian note unless `--force` was intentional.
|
package/CLAUDE.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
Use this file as a project-local behavior guide for Claude Code agents.
|
|
4
|
-
|
|
5
|
-
## Operating Principles
|
|
6
|
-
|
|
7
|
-
- Read the repository instructions before editing.
|
|
8
|
-
- Keep changes small and directly tied to the task.
|
|
9
|
-
- Prefer tests before behavior changes.
|
|
10
|
-
- Explain uncertainty instead of guessing silently.
|
|
11
|
-
- Do not overwrite user work unless explicitly asked.
|
|
12
|
-
|
|
13
|
-
## Completion Criteria
|
|
14
|
-
|
|
15
|
-
Before reporting completion, run the smallest command that proves the change works and summarize the result.
|