aidimag 1.0.9 → 1.0.11

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.
@@ -73,11 +73,10 @@ This project uses aiDimag for persistent memory. Always consult memory before pr
73
73
  const current = existsSync(rootIgnore) ? readFileSync(rootIgnore, "utf8") : "";
74
74
  const folder = resolveKnowledgeConfig(root).folder;
75
75
  const additions = [];
76
- if (!current.includes(".aidimag/memory.db"))
77
- additions.push(".aidimag/memory.db*");
78
- // keep dropped knowledge docs (may contain secrets) out of git, but track the folder
79
- if (!current.includes(`${folder}/*`))
80
- additions.push(`${folder}/*`, `!${folder}/.gitkeep`);
76
+ if (!current.includes(".aidimag"))
77
+ additions.push(".aidimag");
78
+ if (!current.includes(folder))
79
+ additions.push(folder);
81
80
  // generated context files (users can commit them if they want, but default is gitignored)
82
81
  if (!current.includes("CLAUDE.md"))
83
82
  additions.push("CLAUDE.md");
@@ -87,6 +86,8 @@ This project uses aiDimag for persistent memory. Always consult memory before pr
87
86
  additions.push(".windsurfrules");
88
87
  if (!current.includes("AGENTS.md"))
89
88
  additions.push("AGENTS.md");
89
+ if (!current.includes(".github/copilot-instructions.md"))
90
+ additions.push(".github/copilot-instructions.md");
90
91
  if (additions.length) {
91
92
  appendFileSync(rootIgnore, `${current.endsWith("\n") || current === "" ? "" : "\n"}${additions.join("\n")}\n`);
92
93
  console.log(`\nUpdated ${rootIgnore} (ignored memory.db + ${folder}/ drops + generated context files)`);
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Team-sync commands: serve, cloud, login, logout, sync, keys.
3
3
  */
4
+ import { readFileSync, writeFileSync } from "node:fs";
4
5
  import { MemoryStore, findRepoRoot } from "../../db/store.js";
5
6
  import { fail, maybeRegenerateContext, openBrowser, createPrompter } from "../shared.js";
7
+ import { configPath } from "../../sync/client.js";
6
8
  export function registerSyncCommands(program) {
7
9
  program
8
10
  .command("serve")
@@ -25,7 +27,7 @@ export function registerSyncCommands(program) {
25
27
  .argument("<action>", "link | unlink | status | remote")
26
28
  .option("-s, --server <url>", "Sync server URL")
27
29
  .option("-b, --brain <name>", "Brain (team memory) name on the server")
28
- .option("-t, --token <token>", "Auth token (stored in ~/.aidimag/credentials.json, NOT the repo)")
30
+ .option("-t, --token <token>", "Auth token (stored in .aidimag/config.json)")
29
31
  .option("--json", "Machine-readable output (remote)")
30
32
  .option("--id <memoryId>", "Show one remote memory by id (remote)")
31
33
  .option("--limit <n>", "Max rows to list (remote)", "20")
@@ -41,13 +43,28 @@ export function registerSyncCommands(program) {
41
43
  if (!opts.server || !opts.brain)
42
44
  fail("usage: dim cloud link --server <url> --brain <name> [--token <token>]");
43
45
  const server = String(opts.server).replace(/\/$/, "");
44
- writeCloudConfig(root, { server, brain: opts.brain });
45
- if (opts.token)
46
- saveToken(server, opts.token);
46
+ // Always store token in project config
47
+ const p = configPath(root);
48
+ let existing = {};
49
+ try {
50
+ existing = JSON.parse(readFileSync(p, "utf8"));
51
+ }
52
+ catch {
53
+ // fresh file
54
+ }
55
+ const newConfig = { ...existing, server, brain: opts.brain };
56
+ if (opts.token) {
57
+ newConfig.token = opts.token;
58
+ }
59
+ writeFileSync(p, JSON.stringify(newConfig, null, 2) + "\n");
47
60
  console.log(`Linked to ${server} (brain: ${opts.brain}).`);
48
- console.log(`Config in .aidimag/config.json (commit it — no secrets inside). Token in ~/.aidimag/credentials.json.`);
49
- if (!opts.token && !getToken(server)) {
50
- console.log("⚠ No token stored yet — pass --token or set AIDIMAG_API_KEY before `dim sync`.");
61
+ console.log(`Config stored in .aidimag/config.json (per-project).`);
62
+ if (!opts.token && !getToken(server, root)) {
63
+ console.log("⚠ No token stored yet — pass --token or set AIDIMAG_API_KEY before \`dim sync\`.");
64
+ }
65
+ else if (opts.token) {
66
+ console.log(`✓ Token stored in .aidimag/config.json`);
67
+ console.log(`⚠ Add .aidimag/config.json to .gitignore if you don't want to commit the token.`);
51
68
  }
52
69
  break;
53
70
  }
@@ -60,8 +77,10 @@ export function registerSyncCommands(program) {
60
77
  const cfg = readCloudConfig(root);
61
78
  if (!cfg)
62
79
  console.log("Not cloud-linked. Use `dim cloud link`.");
63
- else
64
- console.log(`server: ${cfg.server}\nbrain: ${cfg.brain}\ntoken: ${getToken(cfg.server) ? "stored" : "MISSING"}`);
80
+ else {
81
+ const token = getToken(cfg.server, root);
82
+ console.log(`server: ${cfg.server}\nbrain: ${cfg.brain}\ntoken: ${token ? "stored in .aidimag/config.json" : "MISSING"}`);
83
+ }
65
84
  break;
66
85
  }
67
86
  case "remote": {
@@ -5,9 +5,9 @@
5
5
  * push: rows changed since last push (+ tombstones) → server
6
6
  * pull: latest remote rows since cursor → apply if remote.updatedAt > local
7
7
  *
8
- * Config split (by design):
9
- * .aidimag/config.json { server, brain } committed to git (no secrets)
10
- * ~/.aidimag/credentials.json { [server]: token } never in the repo
8
+ * Config (per-project):
9
+ * .aidimag/config.json { server, brain, token } per-project, add to .gitignore if token is sensitive
10
+ * AIDIMAG_API_KEY env var alternative to storing token in config
11
11
  */
12
12
  import type { MemoryStore } from "../db/store.js";
13
13
  import type { RemoteSnapshot } from "./server.js";
@@ -20,7 +20,7 @@ export declare function syncMetaKey(base: string, brain: string): string;
20
20
  export declare function configPath(repoRoot: string): string;
21
21
  export declare function readCloudConfig(repoRoot: string): CloudConfig | null;
22
22
  export declare function writeCloudConfig(repoRoot: string, cfg: CloudConfig): void;
23
- export declare function getToken(server: string): string | null;
23
+ export declare function getToken(server: string, repoRoot?: string): string | null;
24
24
  export declare function saveToken(server: string, token: string): void;
25
25
  export declare function removeToken(server: string): boolean;
26
26
  export interface DeviceStart {
@@ -5,9 +5,9 @@
5
5
  * push: rows changed since last push (+ tombstones) → server
6
6
  * pull: latest remote rows since cursor → apply if remote.updatedAt > local
7
7
  *
8
- * Config split (by design):
9
- * .aidimag/config.json { server, brain } committed to git (no secrets)
10
- * ~/.aidimag/credentials.json { [server]: token } never in the repo
8
+ * Config (per-project):
9
+ * .aidimag/config.json { server, brain, token } per-project, add to .gitignore if token is sensitive
10
+ * AIDIMAG_API_KEY env var alternative to storing token in config
11
11
  */
12
12
  import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "node:fs";
13
13
  import { homedir } from "node:os";
@@ -55,18 +55,24 @@ export function writeCloudConfig(repoRoot, cfg) {
55
55
  function credentialsPath() {
56
56
  return path.join(homedir(), ".aidimag", "credentials.json");
57
57
  }
58
- export function getToken(server) {
58
+ export function getToken(server, repoRoot) {
59
59
  if (process.env.AIDIMAG_API_KEY)
60
60
  return process.env.AIDIMAG_API_KEY;
61
- const p = credentialsPath();
62
- if (!existsSync(p))
63
- return null;
64
- try {
65
- return JSON.parse(readFileSync(p, "utf8"))[server] ?? null;
66
- }
67
- catch {
68
- return null;
61
+ // Always use project-level config
62
+ if (repoRoot) {
63
+ const projectConfigPath = configPath(repoRoot);
64
+ if (existsSync(projectConfigPath)) {
65
+ try {
66
+ const cfg = JSON.parse(readFileSync(projectConfigPath, "utf8"));
67
+ if (cfg.token)
68
+ return cfg.token;
69
+ }
70
+ catch {
71
+ // ignore
72
+ }
73
+ }
69
74
  }
75
+ return null;
70
76
  }
71
77
  export function saveToken(server, token) {
72
78
  const p = credentialsPath();
@@ -162,7 +168,7 @@ export async function fetchRemoteSnapshot(repoRoot, opts = {}) {
162
168
  const cfg = readCloudConfig(repoRoot);
163
169
  if (!cfg)
164
170
  throw new Error("repo is not cloud-linked. Run `dim cloud link --server <url> --brain <name> --token <token>` first.");
165
- const token = getToken(cfg.server);
171
+ const token = getToken(cfg.server, repoRoot);
166
172
  if (!token)
167
173
  throw new Error(`no credentials for ${cfg.server}. Run \`dim cloud link\` with --token, or set AIDIMAG_API_KEY.`);
168
174
  const q = new URLSearchParams({ brain: cfg.brain });
@@ -203,7 +209,7 @@ export async function sync(store, repoRoot, opts = {}) {
203
209
  const cfg = readCloudConfig(repoRoot);
204
210
  if (!cfg)
205
211
  throw new Error("repo is not cloud-linked. Run `dim cloud link --server <url> --brain <name> --token <token>` first.");
206
- const token = getToken(cfg.server);
212
+ const token = getToken(cfg.server, repoRoot);
207
213
  if (!token)
208
214
  throw new Error(`no credentials for ${cfg.server}. Run \`dim cloud link\` with --token, or set AIDIMAG_API_KEY.`);
209
215
  const cursorKey = syncMetaKey(CURSOR_KEY, cfg.brain);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidimag",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Persistent, verified memory for AI coding agents. CLI: dim.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",